• 面向对象之元类编程


    面向对象之元类编程

    一切皆对象

    函数是对象


     

      在Python中,一切皆为对象。我们之前常用的函数,本身也是一个实例化对象。

     

    # ==== 函数也是对象 ====
    
    def func():
        pass
    
    print(func.__class__)
    
    # ==== 我们还可以为函数对象进行增加属性的操作:虽然没人这样做 ====
    
    func.x = "随便写的: x"
    func.y = "随便写的: y"
    
    print(func.x)
    print(func.y)
    
    # ==== 执行结果 ==== Ps:可以看到,函数是类function的实例对象。这充分验证了Python中一切皆对象的概念。
    
    """
    <class 'function'>
    随便写的: x
    随便写的: y
    """

     

    类也是对象


     

      我们使用class定义出的类,本身也是某一个类的实例对象... 我知道这可能很颠覆三观。但是事实如此...

      type类是所有类的元类,即metaclass...

     

    # ==== 类也是对象 ====
    
    class Foo(object):
        pass
    
    print(Foo.__class__)
    print(object.__class__)
    print(dict.__class__)
    print(list.__class__)
    
    # ==== 执行结果 ====  Ps:可以看见,不管是自定义的类,还是Python自带的类,甚至是object类,都是type类的实例对象。
    
    """
    <class 'type'>
    <class 'type'>
    <class 'type'>
    <class 'type'>
    """

     

    什么是元类


     

      经过上面的例子,我们可以看到所有类都是由type类进行实例化而得到的,因此。元类即是对类进行实例化的类。

      元类的作用:

        1.用于生产出类对象

        2.用于生产出类对象的实例对象

     

    浅析class内部机制

    一个类定义后发生的4件事


     

      我们使用class定义一个类,实际底层都是由type类进行实例化的。那么对于一个类定义后发生的事情总体有以下四步:

        1.拿到类名。

        2.拿到类的基类。

        3.执行类体代码,创建类的局部命名空间。

        4.调用元类,默认为type类,得到实例化出的类对象。

     

    # ==== 常规定义类 ====
    
    class People(object):
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def say(self):
            print("{0}:{1}".format(self.name, self.age))
    
    # ==== 测试 ====
    
    print(People.__dict__)
    p1 = People("Yunya",18)
    p1.say()
    
    # ==== 执行结果 ====
    
    """
    {'__module__': '__main__', '__init__': <function People.__init__ at 0x0000029FBF9DFF70>, 'say': <function People.say at 0x0000029FC7DA6700>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
    Yunya:18
    """
    常规定义类
    # ==== 一个类定义后发生的4件事 ====
    
    # 类有三大特征:
    
    # 1、类名
    class_name = "People"
    
    # 2、类的基类
    class_bases = (object,) # <-- 注意,必须逗号分隔。tuple类型
    
    # 3、执行类体代码拿到类的命名空间
    class_dic = {} # <-- 类的局部命名空间
    class_body = """
    
    def __init__(self,name,age):
        self.name = name
        self.age = age
    
    def say(self):
        print("{0}:{1}".format(self.name,self.age))
    
    """
    
    exec(class_body,{},class_dic)  # 位置1:执行的代码,位置2:全局命名空间,位置3,执行代码期间出现的局部命名空间的变量全部存放于此
    
    print(class_dic)  # 该字典中存放了类中的变量名对应的内存地址。
    
    # 4、调用元类
    People = type(class_name,class_bases,class_dic)
    
    # ==== 测试 ====
    
    p1 = People("Yunya",18)
    p1.say()  # 可以成功执行!
    
    # ==== 执行结果 ==== Ps:虽然可以用这种方式来实例化出类对象,但是我们并不推荐这样做。相比于直接使用class机制,这样做法显得复杂并且对比class_dic与People.__dict__来看明显使用class机制会完善的多。
    
    """
    {'__init__': <function __init__ at 0x000001908A7B61F0>, 'say': <function say at 0x000001908AA0FF70>}
    Yunya:18
    """
    一个类定义后发生的4件事
    #exec:三个参数
    
    #参数一:包含一系列python代码的字符串
    
    #参数二:全局作用域(字典形式),如果不指定,默认为globals()
    
    #参数三:局部作用域(字典形式),如果不指定,默认为locals()
    
    #可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
    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}
    exec参数补充

     

    自定义元类

    元类如何生产出类对象


     

      我们继续探讨关于上节标黄的地方。元类如何生产出类对象。

     

      元类生产出类对象会经过两个步骤:

        1.调用 元类自己的__new__ 方法(创建出空的对象)

        2.调用 元类自己的__init__ 方法(将空对象构造为类对象,可以理解为为其__dict__填充数据)

     

      注意:

        __new__方法其实是在__init__方法之前执行的。因为我们之前在学习__init__方法中可以看到参数self是一个空对象,

        这个空对象实际上就是__new__制造出的。

     

    # ==== 元类如何生产出类对象 ====
    
    class MetaClass(type):  # <-- 必须继承type类,才会被视为一个元类
        """自定义元类"""
    
        def __new__(cls, *args, **kwargs):
            """
            cls --->  类本身(空的)
            args --->   ("类名",(类的继承关系,),{类的局部命名空间})
            kwargs --->  空
            """
            print("MetaClass.__new__ --> cls 
    ",cls)
            print("MetaClass.__new__ --> args 
    ",args)
            print("MetaClass.__new__ --> kwargs 
    ",kwargs)
            # return super().__new__(cls,*args,**kwargs) #  注意,此时必须将属性全部传入。因为现在的对象是个空对象,或者可以采取下面的方式
            return type.__new__(cls,*args,**kwargs)  # 让父类 type 为我们造出空对象
    
        def __init__(self,*args,**kwargs):
            """
            cls --->  类本身(空的)
            args --->   ("类名",(类的继承关系,),{类的局部命名空间})
            kwargs --->  空
            """
            print("MetaClass.__init__ --> self 
    ",self)
            print("MetaClass.__init__ --> args 
    ",args)
            print("MetaClass.__init__ --> kwargs 
    ",kwargs)
            super(MetaClass, self).__init__(*args,**kwargs)  # 让父类 __init__ 为我们将空对象转换为类对象
    
    
    class People(object,metaclass=MetaClass):
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def say(self):
            print("{0}:{1}".format(self.name, self.age))
    
    
    
    # ==== 执行结果 ====
    
    """
    MetaClass.__new__ --> cls 
     <class '__main__.MetaClass'>
    MetaClass.__new__ --> args 
     ('People', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'People', '__init__': <function People.__init__ at 0x000001FC34DC7670>, 'say': <function People.say at 0x000001FC34DC7790>})
    MetaClass.__new__ --> kwargs 
     {}
    MetaClass.__init__ --> self 
     <class '__main__.People'>
    MetaClass.__init__ --> args 
     ('People', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'People', '__init__': <function People.__init__ at 0x000001FC34DC7670>, 'say': <function People.say at 0x000001FC34DC7790>})
    MetaClass.__init__ --> kwargs 
     {}
    """
    类如何生产出类对象

    img

     

    元类如何生产出类对象的实例对象


     

      当造好的类对象试图使用 类名() 的方式进行实例化,会发生以下几件事。

     

        1.类对象调用元类下的 __call__ 方法。

        2.元类下的__call__方法会去调用类对象下的__new__方法创建空对象

        3.元类下的__call__方法会去调用类对象下的__init__方法将空对象转换为类对象的实例对象

        4.元类下的__call__方法将返回造好的类对象的实例对象。

     

      关于__call__方法,试图使用 名字() 的方式将触发其实例化出自己类中的 __call__ 方法,有则执行,没有则抛出异常。

      比如我们定义的一个函数,使用函数名()的方式就代表是去调用元类type中的__call__方法,然后再到function类中去执行其__new__方法以及__init__方法。

     

    # ==== 有__call__方法 ====
    
    class People(object):
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def say(self):
            print("{0}:{1}".format(self.name, self.age))
    
        def __call__(self, *args, **kwargs):
            print("调用了__call__")
    
    # ==== 测试 ====
    
    p1 = People("Yunya",18)
    p1()
    
    # ==== 执行结果 ==== Ps:没有抛出异常了。
    
    """
    调用了__call__
    """
    有__call__方法
    # ==== 无__call__方法 ====
    
    class People(object):
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def say(self):
            print("{0}:{1}".format(self.name, self.age))
    
    # ==== 测试 ====
    
    p1 = People("Yunya",18)
    p1()
    
    # ==== 执行结果 ==== Ps:由于People类中没有定义__call__方法,则其实例化对象不能使用 名字() 的方式进行调用
    
    """
    Traceback (most recent call last):
      File "C:/Users/Administrator/PycharmProjects/learn/反射与自省.py", line 15, in <module>
        p1()
    TypeError: 'People' object is not callable
    """
    无__call__方法
    # ==== 元类如何生产出类对象的实例对象 ====
    
    class MetaClass(type):  # <-- 必须继承type类,才会被视为一个元类
        """自定义元类"""
    
        def __call__(self, *args, **kwargs):
            """
            self    -->  类对象本身(空的)  <class '__main__.People'>
            args    -->  传入的参数。  ('Yunya', 18)
            kwargs  -->  空的,{}
            """
            # # 构造出空对象
            obj = self.__new__(self, *args, **kwargs)
            print(obj) # <__main__.People object at 0x0000020AEC3A02E0>
            print("__new__过后的对象的__dict__",obj.__dict__)
            # 将空对象转换为类对象People的实例对象
            self.__init__(obj,*args,**kwargs)
            print("__init__过后的对象的__dict__",obj.__dict__)
            # 将类对象People的实例对象进行返回
            return obj
    
    
    
    class People(object,metaclass=MetaClass):
    
        def __new__(cls, *args, **kwargs):
            return object.__new__(cls)
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def say(self):
            print("{0}:{1}".format(self.name, self.age))
    
    
    p1 = People("Yunya",18) # 相当于调用元类下的 __call__
    print(p1.name)
    print(p1.age)
    
    # ==== 执行结果 ====
    
    """
    <__main__.People object at 0x0000020AEC3A02E0>
    __new__过后的对象的__dict__ {}
    __init__过后的对象的__dict__ {'name': 'Yunya', 'age': 18}
    Yunya
    18
    """
    元类如何生产出类对象的实例对象

     

    img

     

      图片来源:深入理解Python元类(原创)

     

    实例对象与类对象的查找顺序

    类对象的属性查找顺序


     

      现在该介绍终极版的属性查找顺序了。

     

      细心的朋友应该发现,为什么类对象可以调用元类的 __call__ , 而类对象的实例对象却不能? 这里其实是有区别的。这里先介绍类对象的属性查找顺序。

     

      类本身 --> 父类 --> object类 --> 元类

     

    # ==== 类对象的属性查找顺序 ====
    
    class MetaClass(type):  # <-- 必须继承type类,才会被视为一个元类
        """自定义元类"""
        name = "MetaClass"
    
    class A(object):
        # name = "A"
        pass
    
    class B(A):
        # name = "B"
        pass
    
    class C(B,metaclass=MetaClass):
        pass
    
    print(C.name)
    
    # ==== 执行结果 ====
    
    """
    MetaClass
    """
    类对象的属性查找顺序

     

    img

     

    实例对象的属性查找顺序


     

      实例对象还是通过MRO列表进行找。但是并不会找到元类中去

     

      实例对象本身 --> 实例化出自己的类 --> 父类 --> object类

     

      所以!实例对象是拿不到元类中的 __call__ 的,故不能进行 名字() 的使用!

     

    # ==== 实例对象属性查找顺序 ====
    
    class MetaClass(type):  # <-- 必须继承type类,才会被视为一个元类
        """自定义元类"""
        name = "MetaClass"
    
    class A(object):
        # name = "A"
        pass
    
    class B(A):
        # name = "B"
        pass
    
    class C(B,metaclass=MetaClass):
        pass
    
    c1 = C()
    print(c1.name)
    
    # ==== 执行结果 ====
    
    """
    Traceback (most recent call last):
      File "C:/Users/Administrator/PycharmProjects/learn/反射与自省.py", line 19, in <module>
        print(c1.name)
    AttributeError: 'C' object has no attribute 'name'
    """
    实例对象属性查找顺序

     

    img

     

    练习题

    类的命名规则检测


     

    class MetaClass(type):  # <-- 必须继承type类,才会被视为一个元类
        """自定义元类"""
    
      def __init__(self,*args,**kwargs):
    
            if not args[0].istitle():
                raise NameError("类名必须是大写!")
    
            super(MetaClass,self).__init__(*args,**kwargs)
    
    
    class student(object,metaclass=MetaClass):
        pass
    
    # ==== 执行结果 ====
    
    """
    Traceback (most recent call last):
      File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 11, in <module>
        class student(object,metaclass=MetaClass):
      File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 6, in __init__
        raise NameError("类名必须是大写!")
    NameError: 类名必须是大写!
    """
    类的命名规则检测

     

    类的文档信息检测


     

    class MetaClass(type):  # <-- 必须继承type类,才会被视为一个元类
        """自定义元类"""
    
      def __init__(self,*args,**kwargs):
    
            if "__doc__" not in args[2]:
                raise SyntaxError("{0}类必须要有文档注释".format(args[0]))  # SyntaxError 语法错误
    
            super(MetaClass,self).__init__(*args,**kwargs)
    
    
    class student(object,metaclass=MetaClass):
        pass
    
    # ==== 执行结果 ====
    
    """
    Traceback (most recent call last):
      File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 11, in <module>
        class student(object,metaclass=MetaClass):
      File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 6, in __init__
        raise SyntaxError("{0}类必须要有文档注释".format(args[0]))  # SyntaxError 语法错误
    SyntaxError: student类必须要有文档注释
    """
    类的文档信息检测

     

    基于元类实现单例模式


     

    # 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间
    # 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了
    #settings.py文件内容如下
    # HOST='1.1.1.1'
    # PORT=3306
    
    #方式一:定义一个类方法实现单例模式
    
    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) # 进行 Mysql.__init__(settings.HOST,settings.PORT)) 并赋值
            return cls.__instance
    
    obj1=Mysql('1.1.1.2',3306)
    obj2=Mysql('1.1.1.2',3306)
    print(obj1 is obj2) #False
    
    obj3=Mysql.singleton()
    obj4=Mysql.singleton()
    print(obj3 is obj4) #True
    
    
    #方式二:定制元类实现单例模式
    import settings
    
    class Mymeta(type):
        def __init__(self,name,bases,dic): #定义类Mysql时就触发
            # 事先先从配置文件中取配置来造一个Mysql的实例出来
            self.__instance = object.__new__(self)  # 产生对象
            self.__init__(self.__instance, settings.HOST, settings.PORT)  # 初始化对象
            # 上述两步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)
            super().__init__(name,bases,dic)
    
        def __call__(self, *args, **kwargs): #Mysql(...)时触发
            if args or kwargs: # args或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() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
    obj2=Mysql()
    obj3=Mysql()
    print(obj1 is obj2 is obj3)
    obj4=Mysql('1.1.1.4',3307)
    
    
    #方式三:定义一个装饰器实现单例模式
    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
    基于元类实现单例模式

     

  • 相关阅读:
    day39
    day36
    day35
    day34
    深入理解css的margin
    git使用
    java常见的分页实现方式
    jquery常识
    与border不得不说的故事
    测试效果
  • 原文地址:https://www.cnblogs.com/Yunya-Cnblogs/p/13141557.html
Copyright © 2020-2023  润新知