I. Preface
Metaclasses belong to the deep magic of python object-oriented programming. 99% of people don't know the point. In fact, some people who think they understand metaclasses only justify themselves and point to the end. In terms of the control of metaclasses, they are full of flaws and logical confusion. Today I'll take you to deeply understand the context of python metaclasses.
Behind the author's simple understanding is his obsession with technology day after day. I hope you can respect the originality and be happy that you can solve all your doubts about metaclasses because of this article!!!
What is a metaclass
Everything comes from one sentence: everything in python is an object. Let's first define a class and then analyze it step by step
class OldboyTeacher(object): school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name)
All objects are obtained by instantiating or calling the class (the process of calling the class is called class instantiation). For example, object t1 is obtained by calling the class OldboyTeacher
t1=OldboyTeacher('egon',18) print(type(t1)) #The class of the viewing object t1 is < class'__ main__. OldboyTeacher'>
If everything is an object, the class OldboyTeacher is essentially an object. Since all objects are obtained by calling a class, OldboyTeacher must also be obtained by calling a class, which is called a metaclass
So we can deduce that = = = > the process of generating OldboyTeacher must have happened: OldboyTeacher = metaclass (...)
print(type(OldboyTeacher)) # The result is < class' type '> and it is proved that the OldboyTeacher is generated by calling the metaclass type, that is, the default metaclass is type
Process analysis of creating class with three class keywords
Based on the concept that everything is an object in python, we analyzed that the class we defined with the class keyword is also an object. The class responsible for generating the object is called metaclass (metaclass can be referred to as class class for short), and the built-in metaclass is type
When the class keyword helps us create a class, it must help us call the metaclass OldboyTeacher=type(...). What are the parameters passed in when calling type? It must be the key component of a class. A class has three components, namely
1. Class name_ name=‘OldboyTeacher’
2. Base class_bases=(object,)
3. Class namespace_ DIC, the class namespace is obtained by executing the class body code
When calling type, the above three parameters will be passed in sequence
To sum up, the class keyword helps us create a class, which should be subdivided into the following four processes
Supplement: usage of exec
#exec: three parameters #Parameter 1: string containing a series of python code #Parameter 2: global scope (dictionary form). If it is not specified, it defaults to global () #Parameter 3: local scope (dictionary form). If it is not specified, it defaults to locals() #The execution of the exec command can be regarded as the execution of a function, and the names generated during the execution will be stored in the local namespace g={ 'x':1, 'y':2 } l={} exec(''' global x,z x=100 z=200 m=300 ''',g,l) print(g) #{'x': 100, 'y': 2,'z':200,......} print(l) #{'m': 300}
V. creation of user-defined metaclass control class OldboyTeacher
A class does not declare its own metaclass. By default, its metaclass is type. In addition to using the built-in metaclass type, we can also define the metaclass by inheriting type, and then specify the metaclass for a class using the metaclass keyword parameter
class Mymeta(type): #Only by inheriting the type class can it be called a metaclass, otherwise it is an ordinary user-defined class pass class OldboyTeacher(object,metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...}) school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name)
Custom meta classes can control the generation of classes. The generation of classes is actually the call process of meta classes, that is, OldboyTeacher=Mymeta("OldboyTeacher", (object), {}). Calling Mymeta first produces an empty object OldoyTeacher, and then passes simultaneous interpreting the parameters in Mymeta brackets to Mymeta under Mymeta. init__ Method to complete the initialization, so we can
class Mymeta(type): #Only by inheriting the type class can it be called a metaclass, otherwise it is an ordinary user-defined class def __init__(self,class_name,class_bases,class_dic): # print(self) #<class '__main__.OldboyTeacher'> # print(class_bases) #(<class 'object'>,) # print(class_dic) #{'__module__': '__main__', '__qualname__': 'OldboyTeacher', 'school': 'oldboy', '__init__': <function OldboyTeacher.__init__ at 0x102b95ae8>, 'say': <function OldboyTeacher.say at 0x10621c6a8>} super(Mymeta, self).__init__(class_name, class_bases, class_dic) # Reuse the function of the parent class if class_name.islower(): raise TypeError('Class name%s Please change to hump body' %class_name) if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0: raise TypeError('Class must have a document comment, and the document comment cannot be empty') class OldboyTeacher(object,metaclass=Mymeta): # OldboyTeacher=Mymeta('OldboyTeacher',(object),{...}) """ class OldboyTeacher Document comments for """ school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name)
Call of six custom metaclass control class OldboyTeacher
Reserve knowledge: call
class Foo: def __call__(self, *args, **kwargs): print(self) print(args) print(kwargs) obj=Foo() #1. To make obj an callable object, you need to define a method in the object's class__ call__ Method, which is automatically triggered when the object is called #2. The return value of calling obj is__ call__ Method res=obj(1,2,3,x=1,y=2)
From the above example, calling an object is the trigger in the class where the object is located__ call__ Method. If OldboyTeacher is also regarded as an object, there must be one in the class of OldboyTeacher__ call__ method
class Mymeta(type): #Only by inheriting the type class can it be called a metaclass, otherwise it is an ordinary user-defined class def __call__(self, *args, **kwargs): print(self) #<class '__main__.OldboyTeacher'> print(args) #('egon', 18) print(kwargs) #{} return 123 class OldboyTeacher(object,metaclass=Mymeta): school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) # Calling OldboyTeacher is in the calling OldboyTeacher class__ call__ method # Then pass OldboyTeacher to self, overflow location parameter to *, and overflow keyword parameter to** # The return value of calling OldboyTeacher is called__ call__ Return value of t1=OldboyTeacher('egon',18) print(t1) #123
By default, calling t1=OldboyTeacher('egon ', 18) does three things
1. Generate an empty object obj
2. Call__ init__ Method initializes object obj
3. Returns the initialized obj
Corresponding to the__ call__ Methods should also do these three things
class Mymeta(type): #Only by inheriting the type class can it be called a metaclass, otherwise it is an ordinary user-defined class def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'> #1. Call__ new__ Generate an empty object obj obj=self.__new__(self) # self here is an OldoyTeacher class, which must pass parameters, representing the obj object that creates an OldboyTeacher #2. Call__ init__ Initialize empty object obj self.__init__(obj,*args,**kwargs) #3. Returns the initialized object obj return obj class OldboyTeacher(object,metaclass=Mymeta): school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) t1=OldboyTeacher('egon',18) print(t1.__dict__) #{'name': 'egon', 'age': 18}
Example above__ call__ Equivalent to a template, we can rewrite it on this basis__ call__ To control the process of calling OldboyTeacher, such as making all the properties of the OldboyTeacher object private
class Mymeta(type): #Only by inheriting the type class can it be called a metaclass, otherwise it is an ordinary user-defined class def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'> #1. Call__ new__ Generate an empty object obj obj=self.__new__(self) # self here is an OldoyTeacher class, which must pass parameters, representing the obj object that creates an OldboyTeacher #2. Call__ init__ Initialize empty object obj self.__init__(obj,*args,**kwargs) # After initialization, obj__ dict__ It's worth it obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()} #3. Returns the initialized object obj return obj class OldboyTeacher(object,metaclass=Mymeta): school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) t1=OldboyTeacher('egon',18) print(t1.__dict__) #{'_OldboyTeacher__name': 'egon', '_OldboyTeacher__age': 18}
The above example involves finding attributes, such as self New, see the next section
Sixth, look at attribute search
Combined with the implementation principle of python inheritance + metaclass, what should the attribute search look like???
After learning metaclasses, in fact, all the classes we customize with class are objects (including the object class itself is also an instance of metaclass type, which can be viewed with type(object)). We have learned the implementation principle of inheritance. If we look at the class as an object, the following inheritance should be said: the object OldboyTeacher inherits the object Foo, and the object Foo inherits the object Bar, Object Bar inherits object object
class Mymeta(type): #Only by inheriting the type class can it be called a metaclass, otherwise it is an ordinary user-defined class n=444 def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'> obj=self.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Bar(object): n=333 class Foo(Bar): n=222 class OldboyTeacher(Foo,metaclass=Mymeta): n=111 school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) print(OldboyTeacher.n) #Annotate n=xxx in each class from bottom to top, and then re run the program. It is found that the search order of n is oldboyteacher - > foo - > bar - > Object - > mymeta - > type
Therefore, attribute search should be divided into two layers. One layer is the search of object layer (MRO based on c3 algorithm), and the other layer is the search of class layer (i.e. metaclass layer)
#Search order: #1. First object layer: oldoyteacher - > foo - > bar - > object #2. Then metaclass layer: mymeta - > type
Based on the above summary, let's analyze the meta class Mymeta__ call__ Self__ new__ Search for
class Mymeta(type): n=444 def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'> obj=self.__new__(self) print(self.__new__ is object.__new__) #True class Bar(object): n=333 # def __new__(cls, *args, **kwargs): # print('Bar.__new__') class Foo(Bar): n=222 # def __new__(cls, *args, **kwargs): # print('Foo.__new__') class OldboyTeacher(Foo,metaclass=Mymeta): n=111 school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) # def __new__(cls, *args, **kwargs): # print('OldboyTeacher.__new__') OldboyTeacher('egon',18) #In the class that triggers OldboyTeacher__ call__ Method, and then execute self__ new__ Start finding
Summary, under Mymeta__ call__ Self new__ I didn't find it in Oldboy teacher, Foo or Bar__ new__ In case of, I will go to the object__ New, and there is one under object by default__ new__, So even the previous classes are not implemented__ new__, You will definitely find one in the object. You won't and don't need to find it in the metaclass Mymeta - > type__ new__
We're in metaclass__ call__ You can also use object New (self) to create objects
But we still recommend__ call__ Use self in New (self) to create an empty object, because this method will retrieve three classes oldboyteacher - > foo - > bar, and object__ new__ Directly across the three of them
Finally
class Mymeta(type): #Only by inheriting the type class can it be called a metaclass, otherwise it is an ordinary user-defined class n=444 def __new__(cls, *args, **kwargs): obj=type.__new__(cls,*args,**kwargs) # This value transfer method must be followed print(obj.__dict__) # return obj # The following is triggered only when the return value is an object of type__ init__ return 123 def __init__(self,class_name,class_bases,class_dic): print('run. . . ') class OldboyTeacher(object,metaclass=Mymeta): #OldboyTeacher=Mymeta('OldboyTeacher',(object),{...}) n=111 school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) print(type(Mymeta)) #<class 'type'> # The process of generating the class OldboyTeacher is to call Mymeta, which is also an object of the type class. Therefore, the reason why Mymeta can be called must be that there is an object in the metaclass type__ call__ method # At least three things also need to be done in this method: # class type: # def __call__(self, *args, **kwargs): #self=<class '__main__.Mymeta'> # obj=self.__new__(self,*args,**kwargs) # An object that generates Mymeta # self.__init__(obj,*args,**kwargs) # return obj
Seven exercises
Exercise 1: in the metaclass, control to make the data attributes of the user-defined class uppercase
class Mymetaclass(type): def __new__(cls,name,bases,attrs): update_attrs={} for k,v in attrs.items(): if not callable(v) and not k.startswith('__'): update_attrs[k.upper()]=v else: update_attrs[k]=v return type.__new__(cls,name,bases,update_attrs) class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #Legend of the Dragon def walk(self): print('%s is walking' %self.name) print(Chinese.__dict__) ''' {'__module__': '__main__', 'COUNTRY': 'China', 'TAG': 'Legend of the Dragon', 'walk': <function Chinese.walk at 0x0000000001E7B950>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None} '''
Exercise 2: controlling custom classes in metaclasses does not require__ init__ method
1. Metaclasses help create objects and initialize them;
2. It is required that the parameters passed during instantiation must be in the form of keywords, otherwise an exception will be thrown TypeError: must use keyword argument
3.key is used as a user-defined class to generate the attributes of the object, and all attributes become uppercase
class Mymetaclass(type): # def __new__(cls,name,bases,attrs): # update_attrs={} # for k,v in attrs.items(): # if not callable(v) and not k.startswith('__'): # update_attrs[k.upper()]=v # else: # update_attrs[k]=v # return type.__new__(cls,name,bases,update_attrs) def __call__(self, *args, **kwargs): if args: raise TypeError('must use keyword argument for key function') obj = object.__new__(self) #Create an object, and self is the class Foo for k,v in kwargs.items(): obj.__dict__[k.upper()]=v return obj class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #Legend of the Dragon def walk(self): print('%s is walking' %self.name) p=Chinese(name='egon',age=18,sex='male') print(p.__dict__)
Exercise 3: in the metaclass, all the attributes related to the object generated by the user-defined class are hidden attributes
class Mymeta(type): def __init__(self,class_name,class_bases,class_dic): #Create control class Foo super(Mymeta,self).__init__(class_name,class_bases,class_dic) def __call__(self, *args, **kwargs): #Controls the call process of Foo, that is, the generation process of Foo objects obj = self.__new__(self) self.__init__(obj, *args, **kwargs) obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()} return obj class Foo(object,metaclass=Mymeta): # Foo=Mymeta(...) def __init__(self, name, age,sex): self.name=name self.age=age self.sex=sex obj=Foo('egon',18,'male') print(obj.__dict__)
Exercise 4: implement singleton pattern based on metaclass
#Step 5: implement singleton pattern based on metaclass # Single instance: that is, a single instance. It means that the result of multiple instantiations of the same class points to the same object, which is used to save memory space # If we read the configuration from the configuration file for instantiation, there is no need to generate objects repeatedly and waste memory when the configuration is the same #settings. The contents of the PY file are as follows HOST='1.1.1.1' PORT=3306 #Method 1: define a class method to implement the singleton pattern import settings class Mysql: __instance=None def __init__(self,host,port): self.host=host self.port=port @classmethod def singleton(cls): if not cls.__instance: cls.__instance=cls(settings.HOST,settings.PORT) return cls.__instance obj1=Mysql('1.1.1.2',3306) obj2=Mysql('1.1.1.3',3307) print(obj1 is obj2) #False obj3=Mysql.singleton() obj4=Mysql.singleton() print(obj3 is obj4) #True #Method 2: customize the metaclass to implement the singleton mode import settings class Mymeta(type): def __init__(self,name,bases,dic): #Triggered when the class Mysql is defined # Take the configuration from the configuration file in advance to create an instance of Mysql self.__instance = object.__new__(self) # Generate object self.__init__(self.__instance, settings.HOST, settings.PORT) # Initialize object # The above two steps can be combined into the following step # self.__instance=super().__call__(*args,**kwargs) super().__init__(name,bases,dic) def __call__(self, *args, **kwargs): #Mysql(...) Time trigger if args or kwargs: # There is a value in args or kwargs obj=object.__new__(self) self.__init__(obj,*args,**kwargs) return obj return self.__instance class Mysql(metaclass=Mymeta): def __init__(self,host,port): self.host=host self.port=port obj1=Mysql() # If no value is passed, the configuration is read from the configuration file by default to instantiate. All instances should point to a memory address obj2=Mysql() obj3=Mysql() print(obj1 is obj2 is obj3) obj4=Mysql('1.1.1.4',3307) #Method 3: define a decorator to implement the singleton mode import settings def singleton(cls): #cls=Mysql _instance=cls(settings.HOST,settings.PORT) def wrapper(*args,**kwargs): if args or kwargs: obj=cls(*args,**kwargs) return obj return _instance return wrapper @singleton # Mysql=singleton(Mysql) class Mysql: def __init__(self,host,port): self.host=host self.port=port obj1=Mysql() obj2=Mysql() obj3=Mysql() print(obj1 is obj2 is obj3) #True obj4=Mysql('1.1.1.3',3307) obj5=Mysql('1.1.1.4',3308) print(obj3 is obj4) #False