Talking about Python class attributes and metaclasses

problem

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

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

Various metaprogramming

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

\ _ \ _ Init \ _ \ _ or \ _ \ _ new \ _ \ _?

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__.

New special method \ _ \ _ prepare \ _ \ _

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.

Metaclass inheritance

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

Uses of metaprogramming

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.

reference

http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html

Recommended Posts

Talking about Python class attributes and metaclasses
About python, class
Talking about old and new Python classes
About Class and Instance
Python: Class and instance variables
About python objects and classes
About Python variables and objects
About Python, len () and randint ()
About Python datetime and timezone
Python class variables and instance variables
About Python and regular expressions
About Python and os operations
Python # About reference and copy
About Python sort () and reverse ()
About installing Pwntools and Python2 series
perl objects and python class part 2.
Python class definitions and instance handling
About python dict and sorted functions
[Python] About Executor and Future classes
About Python, from and import, as
(Personal notes) Python metaclasses and metaprogramming
About _ and __
Python basic operation 3rd: Object-oriented and class
[Python] Difference between class method and static method
A story about Python pop and append
About python slices
About python comprehension
[Python] class, instance
"Kanrika" python class
About Python tqdm.
About python yield
[python] Difference between variables and self. Variables in class
I wrote a class in Python3 and Java
About python inheritance
About python, range ()
Trouble with Python pseudo-private variables and class inheritance
About python decorators
Python class, instance
A story about modifying Python and adding functions
[Python] Learn about asynchronous programming and event loops
[Python] About multi-process
About the * (asterisk) argument of python (and itertools.starmap)
About shallow and deep copies of Python / Ruby
[Python] Class type and usage of datetime module
#Python basics (class)
Getting Started with python3 # 2 Learn about types and variables
[Python] Convert general-purpose container and class to each other
About creating and modifying custom themes for Python IDLE
Sample of getting module name and class name in Python
[Python] Chapter 01-02 About Python (Execution and installation of development environment)
[python] Compress and decompress
About Python for loops
python syslog wrapper class
Summary about Python scraping
Python class (Python learning memo ⑦)
Python and numpy tips
[Python] pip and wheel
case class in python
Batch design and python
Python iterators and generators
[Python] Class inheritance (super)