为什么从Base继承的SQLAlchemy类不需要构造函数?

时间:2013-12-21 03:17:05

标签: python sqlalchemy

使用从Base类继承的SQLAlchemy对象,我可以将参数传递给类,而不是在构造函数中定义变量:

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
    name = Column(String(50))
    fullname = Column(String(50))
    password = Column(String(12))

    def __repr__(self):
        return "<User(name='%s', fullname='%s', password='%s')>" % (
                                self.name, self.fullname, self.password)

ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')

当然,如果我尝试将这样的参数传递给另一个类,以同样的方式设置类变量我会收到错误:

In [1]: class MyClass(object):
   ...:     i = 2
   ...:     

In [2]: instance = MyClass(i=9)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-6d3ec8445b00> in <module>()
----> 1 instance = MyClass(i=9)

TypeError: object.__new__() takes no parameters

SQLalchemy在做什么样的诡计?他们为什么不只使用构造函数(即__init__方法)?

1 个答案:

答案 0 :(得分:11)

你实际上在这里问了两个问题。

首先,你问“SQLAlchemy在做什么样的诡计”。幕后使用名为Metaclasses的概念动态创建Base类,还有更多内容。

但实际上你需要知道的是,SQLAlchemy在动态设置元素的Base类中定义了一个构造函数(尽管是以迂回的方式)。 Here实际上是该方法的实现(至少在答案时存在):

def _declarative_constructor(self, **kwargs):
    """A simple constructor that allows initialization from kwargs.

    Sets attributes on the constructed instance using the names and
    values in ``kwargs``.

    Only keys that are present as
    attributes of the instance's class are allowed. These could be,
    for example, any mapped columns or relationships.
    """
    cls_ = type(self)
    for k in kwargs:
        if not hasattr(cls_, k):
            raise TypeError(
                "%r is an invalid keyword argument for %s" %
                (k, cls_.__name__))
        setattr(self, k, kwargs[k])

基本上,这是dynamically determining the keyword arguments,并自动设置新对象的属性。您可以将Base类看作如下所示,但请记住它实际上有点复杂(您可以阅读代码以了解更多信息):

class Base(object):
    def __init__(self, **kwargs):
        cls_ = type(self)
        for k in kwargs:
            if not hasattr(cls_, k):
                raise TypeError(
                    "%r is an invalid keyword argument for %s" %
                    (k, cls_.__name__))
            setattr(self, k, kwargs[k])

如果您创建了上述代码,那么您创建的任何继承自Base的类将自动获得自动填充属性的属性,只要该属性已在类上定义。这是因为典型的Python面向对象的继承结构:如果您没有在User对象上定义方法,Python会查找在基类上定义的方法(在本例中为Base }方法)并将使用它。这适用于__init__方法,就像任何其他方法一样。

你的第二个问题是“他们为什么不使用构造函数(即__init__方法)?”好吧,正如我们上面所描述的那样,他们做到了!他们将_declarative_constructor方法设置为__init__Base类,有效地设置了对象构造的默认逻辑。当然,这只定义了默认值;你总是可以覆盖这个,如果你想......

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, Sequence('user_id_seq'), primary_key=True)
    name = Column(String(50))
    fullname = Column(String(50))
    password = Column(String(12))

    def __init__(self, name):
        self.name = name
        self.fullname = name
        self.password = generate_new_password()

# The following will now work...
ed_user = User('ed')
mark_user = User(name='mark')

# ...but this will not...
problem_user = User(name='Error', fullname='Error M. McErrorson', password='w00t')