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 codefrom 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"""