Why do SQLAlchemy classes inheriting from Base not need a constructor?

python, sqlalchemy

Solution

You actually asked two questions here.

First, you asked "What sort of trickery is SQLAlchemy doing". There is a bit more going on behind the scenes using a concept called Metaclasses to dynamically create the `Base` class.

But in reality all you need to know is that SQLAlchemy is defining a constructor (albeit in a roundabout way) in the `Base` class that dynamically sets the elements. Here is actually the implementation of that method (at least as it exists at the time of this answer):

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])

Basically, this is dynamically determining the keyword arguments, and setting the attributes on the new object automatically. You can imagine the`Base` class as looking like the following, although keep in mind it is actually a bit more complex (you can read the code to find out more):

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])

If you created the above code, any class that you create that inherits from `Base` would automatically get the ability to have the attributes of the property auto-filled as long as the attribute was already defined on the class. This is because of the typical Python Object-Oriented inheritance structure: if you don't define a method on your `User` object, Python looks for the method being defined on a base class (in this case the `Base` method) and will use that instead. This goes for the `__init__` method, just like any other method.

Your second question is "Why don't they just use a constructor (i.e an `__init__` method)?" Well, as we've described above, they do! They set the `_declarative_constructor` method to the `__init__` attribute the `Base` class, effectively setting the default logic for object construction. Of course, this only defines the default; you can always override this if you want to...

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')

Problem

With SQLAlchemy objects inheriting from the `Base` class I can pass arguments to a class for variables which aren't defined in a constructor: ``` 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') ``` of course, if I tried passing arguments like that to another class, to set class variables in the same way I'd get an error: ``` 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 ``` What sort of trickery is SQLalchemy doing? Why don't they just use a constructor (i.e an `__init__` method)?

Original source

Related problems