Metaclasses are rarely used unless they are libraries, but they are used in SQLAlchemy, so I will summarize them for reference only.
A metaclass is a class type. In the case of python, the type of the user-defined class is basically type.
>>> class A(object):
>>> pass
>>>
>>> type(A)
type
Metaclasses other than type may be used for special purposes.
A familiar use of metaclasses is in SQLAlchemy's declarative.
>>> from sqlalchemy.ext.declarative import declarative_base
>>> Base = declarative_base()
>>> type(Base)
<class 'sqlalchemy.ext.declarative.api.DeclarativeMeta'>
With declarative, the class definition is automatically linked to the table definition.
class User(Base):
__tablename__ = 'user_account'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Varchar(100))
Internally, it uses its own metaclass called sqlalchemy.ext.declarative.api.DeclarativeMeta, and hooks the process at the time of class definition to perform mapping process.
The definition of sqlalchemy.ext.declarative.api.DeclarativeMeta is as follows. init is called when a class that inherits Base is created, and the class and table are linked in this.
sqlalchemy/ext/declarative/api.py
class DeclarativeMeta(type):
def __init__(cls, classname, bases, dict_):
if '_decl_class_registry' not in cls.__dict__:
_as_declarative(cls, classname, cls.__dict__)
type.__init__(cls, classname, bases, dict_)
def __setattr__(cls, key, value):
_add_attribute(cls, key, value)
In python, it seems that using a metaclass is a relatively common method when you want to do something automatically from the class definition. For example, the Form class of wtforms does much the same thing.
Depending on the use case, you may want to stop this process. Let's consider a use case such as associating a Table object created by reflection from DB with a class created by declarative_base. In such cases, the following code can be used.
from sqlalchemy import MetaData, Table
from sqlalchemy.ext.declarative import api, declarative_base
from sqlalchemy.ext.declarative.api import DeclarativeMeta
class_registry = {}
metadata = MetaData()
Base = declarative_base(class_registry=class_registry,
metadata=MetaData())
class MyMetaClass(DeclarativeMeta):
def __init__(cls, classname, bases, dict_):
#Do not associate with the table
type.__init__(cls, classname, bases, dict_)
@classmethod
def create_class(cls, table_name, class_name, dbsession):
tableobj = Table(table_name, metadata, autoload=True,
autoload_with=dbsession.bind)
class_ = MyMetaClass(class_name, (Base), {})
#Explicitly associate with the table below.
class_.__tablename__ = tablename
class_.__table__ = tableobj
for c in tableobj.columns:
setattr(class_, c.name, c)
api.instrument_declarative(class_, class_registry, metadata)
return class_
Recommended Posts