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 theBaseclass. I want to add a class attribute calledALL_PARAMS that integrates the parent class BASE_PARAMSand my ownPARAMS 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