Deep understanding of Python__ init_subclass__

origin

When studying the source code of graphql python__ init_subclass_with_meta__ This class method attracts, and then finds another way to change the behavior of subclasses in addition to metaclasses:__ init_subclass__

Class method__ init_subclass__ Introduced from 3.6, the function is to change the behavior of subclasses without using metaclasses. In other words, it is independent of metaclass programming and can also be a means of editing other classes.

Example 1

# defining a SuperClass
class SuperClass:
  
     # defining __init_subclass__ method
    def __init_subclass__(cls, **kwargs):
        cls.default_name ="Inherited Class"
  
# defining a SubClass
class SubClass(SuperClass):
  
     # an attribute of SubClass
    default_name ="SubClass" 
    print(default_name)
  
subclass = SubClass()
print(subclass.default_name)

output

SubClass
Inherited Class

Understand code

  • In the above example, there are two classes (i.e. superclass and subclass), and the subclass inherits from the superclass. default_name is an attribute of the subclass.
  • Property default_ The value of name is used by SuperClass__ init_subclass__ Method change.
  • cls is an inherited subclass. The keyword parameter (* * kwargs) provided to the new class will be passed to the class of the parent class__ init_subclass__.
  • For and use__ init_subclass__ Other subclasses of are compatible. You should take out the required keyword parameters and pass other subclasses to the base class (Super Class).

This__ init_subclass__ Subclasses are very similar to Decorator classes. However, if class decorators only affect the specific class to which they apply, but__ init_subclass__ Applies only to future subclasses of the class that defines the method. This means that we can change / define the behavior of any new class inherited from the superclass.

Example 2

# defining a SuperClass
class SuperClass:
	def __init_subclass__(cls, default_name, **kwargs):
		cls.default_name = default_name

# defining a subclass
class SubClass1(SuperClass, default_name ="SubClass1"):
	pass

# defining another subclass
class SubClass2(SuperClass, default_name ="SubClass2"):
	default_name = "InheritedClass"


# references for subclasses
subClass1 = SubClass1()
subClass2 = SubClass2()

print(subClass1.default_name)
print(subClass2.default_name)

output

SubClass1
SubClass2

Custom steps after creating class objects

Despite__ init_subclass__ It is programmed independently of metaclass, but classes are created by the default metaclass type__ new__ () what steps will follow after creating a class:

  • First, type__ new__ Collect set defined by class namespace_ All descriptors of the name() method;
  • Second, these__ set_name__ The specific descriptor of is called under specific circumstances;
  • Finally, call the hook on the parent class__ init_subclass__ ().

If the class is decorated by the decorator, the above generated object is passed to the class decorator.

summary

In general__ init_subclass__ () is a hook function, which solves the problem of how to let the parent class know that it is inherited. Hooks can change the behavior of classes without resorting to metaclasses or class decorators. Hooks are also simpler to use and easier to understand.
Although this article also mentioned__ set_name__ , But it and__ init_subclass__ Not interrelated__ set_name__ It mainly solves how to let the descriptor know the name of its attribute.

__ init_subclass__ The goal of is to provide a simpler customization method, which is an alternative to metaclasses in simple scenarios. It's worth trying.

The following is the use of this method by graphql python, which is worth learning

Click to view the code
from inspect import isclass

from .props import props


class SubclassWithMeta_Meta(type):
    _meta = None

    def __str__(cls):
        if cls._meta:
            return cls._meta.name
        return cls.__name__

    def __repr__(cls):
        return f"<{cls.__name__} meta={repr(cls._meta)}>"


class SubclassWithMeta(metaclass=SubclassWithMeta_Meta):
    """This class improves __init_subclass__ to receive automatically the options from meta"""

    def __init_subclass__(cls, **meta_options):
        """This method just terminates the super() chain"""
        _Meta = getattr(cls, "Meta", None)
        _meta_props = {}
        if _Meta:
            if isinstance(_Meta, dict):
                _meta_props = _Meta
            elif isclass(_Meta):
                _meta_props = props(_Meta)
            else:
                raise Exception(
                    f"Meta have to be either a class or a dict. Received {_Meta}"
                )
            delattr(cls, "Meta")
        options = dict(meta_options, **_meta_props)

        abstract = options.pop("abstract", False)
        if abstract:
            assert not options, (
                "Abstract types can only contain the abstract attribute. "
                f"Received: abstract, {', '.join(options)}"
            )
        else:
            super_class = super(cls, cls)
            if hasattr(super_class, "__init_subclass_with_meta__"):
                super_class.__init_subclass_with_meta__(**options)

    @classmethod
    def __init_subclass_with_meta__(cls, **meta_options):
        """This method just terminates the super() chain"""

Tags: Python graphql

Posted on Sun, 28 Nov 2021 04:12:16 -0500 by cola