¿Por qué las clases SQLAlchemy heredadas de la Base no necesitan un constructor?

Con los objetos SQLAlchemy heredados de la clase Base , puedo pasar argumentos a una clase para variables que no están definidas en un 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 "" % ( self.name, self.fullname, self.password) ed_user = User(name='ed', fullname='Ed Jones', password='edspassword') 

por supuesto, si intentara pasar argumentos así a otra clase, para establecer variables de clase de la misma manera obtendría un error:

 In [1]: class MyClass(object): ...: i = 2 ...: In [2]: instance = MyClass(i=9) --------------------------------------------------------------------------- TypeError Traceback (most recent call last)  in () ----> 1 instance = MyClass(i=9) TypeError: object.__new__() takes no parameters 

¿Qué clase de trucos hace SQLalchemy? ¿Por qué no solo usan un constructor (es decir, un método __init__ )?

En realidad hiciste dos preguntas aquí.

Primero, preguntas “¿Qué tipo de engaño está haciendo SQLAlchemy”. Hay un poco más de cosas detrás de escena utilizando un concepto llamado Metaclasses para crear dinámicamente la clase Base .

Pero en realidad, todo lo que necesita saber es que SQLAlchemy está definiendo un constructor (aunque de una manera indirecta) en la clase Base que establece los elementos dinámicamente. Aquí está la implementación de ese método (al menos como existe en el momento de esta respuesta):

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

Básicamente, esto es determinar dinámicamente los argumentos de palabras clave y establecer los atributos en el nuevo objeto automáticamente. Puede imaginar la clase Base como la siguiente, aunque tenga en cuenta que en realidad es un poco más compleja (puede leer el código para obtener más información):

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

Si creó el código anterior, cualquier clase que cree que hereda de Base obtendrá automáticamente la capacidad de que los atributos de la propiedad se completen automáticamente siempre que el atributo ya esté definido en la clase. Esto se debe a la típica estructura de herencia orientada a objetos de Python: si no define un método en su objeto User , Python busca el método que se define en una clase base (en este caso, el método Base ) y lo utilizará . Esto se __init__ método __init__ , al igual que cualquier otro método.

Su segunda pregunta es “¿Por qué no usan un constructor (es decir, un método __init__ )?” Bueno, como hemos descrito anteriormente, lo hacen! Establecieron el método _declarative_constructor en el atributo __init__ la clase Base , estableciendo efectivamente la lógica predeterminada para la construcción del objeto. Por supuesto, esto solo define el valor por defecto; siempre puedes anular esto si quieres …

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