【问题标题】:Why do SQLAlchemy classes inheriting from Base not need a constructor?为什么从 Base 继承的 SQLAlchemy 类不需要构造函数?
【发布时间】:2013-12-21 03:17:04
【问题描述】:

使用继承自 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__ 方法)?

【问题讨论】:

  • 大概 Base 类使用*args/**kwargs 接受任意参数,然后将它们与字段匹配。

标签: python sqlalchemy


【解决方案1】:

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

首先,您问“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')

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-28
    • 2013-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    相关资源
    最近更新 更多