Metaclass and singleton pattern

Keywords: Python

Metaclass

Everything in python is an object, so the class you define must also be an object, which is instantiated by a class. This tired class is called metaclass. How to find metaclass

class People:
	def __init__(self,name,age):
		self.name=name
		self.age=age

print(type(People))  # The result is < class' type '>, which proves that People are generated by calling the metaclass type, that is, the default metaclass is type)

When the class keyword helps us create a class, it must help us call the metaclass People=type(...). What are the parameters passed in when People calls type? It must be the key component of a class. A class has three components, namely
1. Class name_ name='StanfordTeacher'
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:

  1. Get the class name class_name='People'
  2. Get the base class of the class_bases=(object,)
  3. Execute the class body code to get the class namespace class_dict={...}
  4. Class People=type(class_name='People',class_bases=(object,),class_dict = {...}) is obtained by metaclass instantiation

Custom metaclasses control the creation of classes

A class does not declare its own metaclass. The default 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 Teacher(object,metaclass=Mymeta): 
    school='Qinghua'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def say(self):
        print('%s says welcome to Qinghua' %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, Teacher=Mymeta('Teacher',(object,) {}). Calling Mymeta first produces an empty object Teacher, and then passes simultaneous interpreting the parameters in Mymeta parenthesis to Mymeta under Mymeta. init__ Method, complete initialization,

class Mymeta(type): 
    def __init__(self,class_name,class_bases,class_dic):
        # print(self)  # <class '__main__.StanfordTeacher'>
        # print(class_bases)  # (<class 'object'>,)
        # print(class_dic)  # {'__module__': '__main__', '__qualname__': 'Teacher', 'school': 'Qinghua', '__init__': <function Teacher.__init__ at 0x102b95ae8>, 'say': <function Teacher.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')

# Teacher=Mymeta('Teacher',(object),{...})
class Teacher(object,metaclass=Mymeta): 
    """
    class Teacher Document comments for
    """
    school='Qinghua'

    def __init__(self,name,age):
        self.name=name
        self.age=age

    def say(self):
        print('%s says welcome to Qinghua' %self.name)

Call of user-defined metaclass control class Teacher

__ call__ Method is automatically triggered when calling the object, and the return value of the calling object is__ call__ Method

class Mymeta(type):  
    def __call__(self, *args, **kwargs):
        print(self)  # <class '__main__.Teacher'>
        print(args)  # ('lili', 18)
        print(kwargs)  # {}
        return 123

class Teacher(object,metaclass=Mymeta):
    school='Qinghua'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def say(self):
        print('%s says welcome to Qinghua' %self.name)

# Calling Teacher is in the calling Teacher class__ call__ method
# Then pass the Teacher to self, the overflow location parameter to *, and the overflow keyword parameter to**
t1=Teacher('lili',18)
print(t1) # 123

Three things happen when calling t1=Teacher('lili',18):

  1. Generate an empty object obj
  2. Call__ init__ Method initializes object obj
  3. Returns the initialized obj

In the Teacher class__ call__ Methods should also do these three things

class Mymeta(type): 
    def __call__(self, *args, **kwargs): 
        # 1. Call__ new__ Generate an empty object obj
        obj=self.__new__(self) # self here is a Teacher class and must pass parameters, representing the object obj that creates a Teacher
        # 2. Call__ init__ Initialize empty object obj
        self.__init__(obj,*args,**kwargs)
        # 3. Returns the initialized object obj
        return obj

class Teacher(object,metaclass=Mymeta):
    school='Qinghua'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def say(self):
        print('%s says welcome to the Stanford to learn Python' %self.name)

t1=Teacher('lili',18)
print(t1.__dict__)  # {'name': 'lili', 'age': 18}

Example above__ call__ Equivalent to a template, we can rewrite it on this basis__ call__ To control the process of calling Teacher, such as making all properties of Teacher objects private

class Mymeta(type):
    def __call__(self, *args, **kwargs):  # The Teacher class is passed in
        obj=self.__new__(self)   # The new method of the parent class of the Teacher class creates the Teacher class
        self.__init__(obj,*args,**kwargs)  # Initialize the Teacher class
        obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()}  # Privatize the properties of the Teacher class
        return obj

class Teacher(object,metaclass=Mymeta):
    school='Qinghua'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def say(self):
        print('%s says welcome to Qinghua' %self.name)

t1=Teacher('lili',18)
print(t1.__dict__)  # {'_Teacher__name': 'lili', '_Teacher__age': 18}

Inheritance + metaclass -- attribute lookup

Suppose the following inheritance relationships exist: object Teacher inherits object Foo, object Foo inherits object Bar, and object Bar inherits object object

It can be seen from the above that classes can also be viewed as objects, so the attribute search should also follow the principle of inheritance: Teacher - > 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: Teacher - > foo - > bar - > object
2. Then metaclass layer: mymeta - > type

Singleton mode

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

Singleton pattern is a common software design pattern. Its core structure contains only a special class called singleton class. The single instance mode can ensure that there is only one instance of a class in the system, and the instance is easy to be accessed by the outside world, so as to facilitate the control of the number of instances and save system resources. If you want only one object of a class to exist in the system, singleton mode is the best solution.

There are three key points of singleton mode:

  • First, a class can only have one instance
  • Second, it must create this instance itself
  • Third, it must provide this example to the whole system by itself

Method 1: with the help of new method
This way is not unique, but the essence is to help_ instance marks the status and judges according to the status

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls,'_instance'):
            orig=super(Singleton,cls)
            cls._instance=orig.__new__(cls,*args,**kwargs)
        return cls._instance

class MyClass(Singleton):
    a=1

one=MyClass()
two=MyClass()

# one and two are identical. You can use id(), = =, is to check
print(one.a)    # 1
print(id(one))  # 2565285375728
print(id(two))  # 2565285375728
print(one == two)   # True
print(one is two)   # True

Method 2: customize the metaclass to implement the singleton mode

"""
class Singleton Medium__init__stay Myclass The declaration is executed Myclass=Singleton()
Myclass()When executing, the parent class is executed first__call__Method( object,Singleton All as Myclass Parent class of,
According to the depth first algorithm, the Singleton Medium__call__(),Singleton Medium__call__()Singleton mode is written)
"""
class Singleton(type):

    def __init__(self, name, bases, dict):
        super(Singleton,self).__init__(name,bases, dict)
        self._instance = None

    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super(Singleton,self).__call__(*args, **kwargs)
        return self._instance

class MyClass(object,metaclass=Singleton):
    a = 1

one=MyClass()
two=MyClass()
print(id(one))  # 1553247294800
print(id(two))  # 1553247294800
print(one == two)   # True
print(one is two)   # True

Method 3: define a decorator to implement the singleton mode

def singleton(cls, *args, **kwargs):
    instances = {}
    
    def _singleton():
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return _singleton

@singleton
class MyClass3(object):
    a = 1

one = MyClass3()
two = MyClass3()

print(id(one))  # 2880466769232
print(id(two))  # 2880466769232
print(one == two)   # True
print(one is two)   # True

class Singleton(object):
    __instance=None

    def __init__(self):
        pass
    def __new__(cls, *args, **kwargs):
        if Singleton.__instance is None:
            Singleton.__instance=object.__new__(cls,*args, **kwargs)
        return Singleton.__instance

one=Singleton()
two=Singleton()
print(id(one))  # 2488569943376
print(id(two))  # 2488569943376
print(one == two)   # True
print(one is two)   # True

There is another way. We all know that the module is imported only once, that is, put the content you want in a py file and import it directly when you need it. It is also an implementation of singleton mode

Posted by p_h_p on Fri, 05 Nov 2021 11:04:31 -0700