class Base:
__BASE_PARAMS__ = ["base"]
class A(Base):
__PARAMS__ = ["a"]
class B(Base):
__PARAMS__ = ["b"]
As mentioned above, there is a Base
class, and the ʻA and
Bclasses inherit from the
Baseclass. I want to add a class attribute called
ALL_PARAMS that integrates the parent class
BASE_PARAMSand my own
PARAMS to the ʻA
and B
classes. That is, we want to obtain the following results.
A.__ALL_PARAMS__
# ['base', 'a']
B.__ALL_PARAMS__
# ['base', 'b']
If implemented normally, it would look like this:
class Base:
__BASE_PARAMS__ = ["base"]
class A(Base):
__PARAMS__ = ["a"]
__ALL_PARAMS__ = Base.__BASE_PARAMS__ + __PARAMS__
class B(Base):
__PARAMS__ = ["b"]
__ALL_PARAMS__ = Base.__BASE_PARAMS__ + __PARAMS__
A.__ALL_PARAMS__
# ['base', 'a']
B.__ALL_PARAMS__
# ['base', 'b']
However, there are many classes that inherit the Base
class, and it is troublesome to write the class attribute __ALL_PARAMS__ = Base.__BASE_PARAMS__ + __PARAMS__
, which is unrelated to processing, in all inherited classes.
Is there a simple way to solve this problem without letting the inherited class write something meaningless? I want you to think about it.
Metaprogramming is a technique for defining a program programmatically.
I won't talk about the basics of metaprogramming here. If you want to know about metaprogramming, please refer to the following articles.
Metaprogramming with Python \ _ \ _ New \ _ \ _ and \ _ \ _ init \ _ \ _ and metaclass In the python metaclass, pass the comparison operator as a string to the ORM method to assemble the where clause.
Now, using the metaclass, we can solve the above problem as follows.
Metaclass for Python3
class MetaClass(type):
def __init__(cls, name, bases, attribute):
super(MetaClass, cls).__init__(name, bases, attribute)
cls.__ALL_PARAMS__ = cls.__BASE_PARAMS__ + getattr(cls, "__PARAMS__", [])
class Base(metaclass=MetaClass):
__BASE_PARAMS__ = ["base"]
class A(Base):
__PARAMS__ = ["a"]
class B(Base):
__PARAMS__ = ["b"]
A.__ALL_PARAMS__
# ['base', 'a']
B.__ALL_PARAMS__
# ['base', 'b']
For Python2, assign a metaclass to the special attribute __metaclass__
.
For Python 2
class Base(object):
__metaclass__ = MetaClass
You can even extend the language with metaprogramming. Final classes in Java don't exist in Python, but metaclasses allow you to define similar functionality.
final_metaclass.py
class FinalMetaClass(type):
def __init__(cls, name, bases, namespace):
super(FinalMetaClass, cls).__init__(name, bases, namespace)
for _class in bases:
if isinstance(_class, FinalMetaClass):
raise TypeError()
class A:
pass
class B(A, metaclass=FinalMetaClass):
pass
# Error!!
class C(B):
pass
Should I use __init__
or __new__
in a metaclass that inherits type?
It's basically a matter of taste, and it doesn't matter which one.
However, __new__
has a higher degree of freedom, such as being able to rewrite __slots__
.
A special method __prepare__
has been added from Python3.
Normally, __dict__
is a dict type whose order is not guaranteed (what happened in Python3.6), and you can control this with __prepare__
.
The following is an excerpt from the Python document.
import collections
class OrderedClass(type):
@classmethod
def __prepare__(metacls, name, bases, **kwds):
return collections.OrderedDict()
def __new__(metacls, name, bases, namespace, **kwds):
cls = type.__new__(metacls, name, bases, dict(namespace))
cls.members = tuple(namespace)
return cls
class A(metaclass=OrderedClass):
def one(self): pass
def two(self): pass
def three(self): pass
def four(self): pass
A.members
# ('__module__', '__qualname__', 'one', 'two', 'three', 'four')
It can be confirmed that the enumeration of class members is in the order of definition.
class MetaA(type):
def __new__(mcls, *args, **kwargs):
cls = type.__new__(mcls, *args, **kwargs)
cls.MetaA = "Yes"
return cls
class MetaB(type):
def __new__(mcls, *args, **kwargs):
cls = type.__new__(mcls, *args, **kwargs)
cls.MetaB = "Yes"
return cls
class A(metaclass=MetaA):
pass
class B(A, metaclass=MetaB):
pass
When I try to define the B
class, I get the following error:
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Not only subclasses of type
can be passed to metaclass
.
Anything that is Callable that corresponds to a specific argument can be used. In this case, a function can be passed to eliminate the diamond inheritance.
class MetaA(type):
def __new__(mcls, *args, **kwargs):
cls = type.__new__(mcls, *args, **kwargs)
cls.MetaA = "Yes"
return cls
class A(metaclass=MetaA):
pass
def MetaB(mcls, *args, **kwargs):
cls = type(mcls, *args, **kwargs)
cls.MetaB = "Yes"
return cls
class B(A, metaclass=MetaAB):
pass
B.MetaA, B.MetaB
# ('Yes', 'Yes')
Metaprogramming is powerful enough to change the language specification. Therefore, heavy use of metaprogramming can be a source of confusion in team programming.
Basically, if the design is solid, metaprogramming should rarely come into play. Exceptionally, when generating the inherited class attribute, it is often possible to write clearly by using a metaclass.
http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html
Recommended Posts