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