• 10 [面向对象]-元类


    1、exec的使用

    # 储备知识
    # 参数1:字符串形式的命令
    # 参数2:全局作用域(字典形式),如果不指定默认使用globals()
    # 参数3:局部作用域(字典形式),如果不指定默认就使用locals()
    
    # print(globals())
    
    
    # 全局作用域
    g = {
        'x': 1,
        'y': 2,  # global x,m
    }
    
    # 局部作用域
    l = {}   # z = 3
    
    
    exec("""
    global x,m
    x = 10
    m = 100
    
    z = 3
    """, g, l)
    
    # global x,m  全局作用域  g
    # z = 3 局部作用域   l
    print(g)
    
    print(l)

     2、一切皆对象:类也是对象

    # 一切皆对象,对象可以怎么用?
    # 1.都可以被引用,x = obj
    # 2.都可以当做函数的参数传入
    # 3.都可以当做函数的返回值
    # 4.都可以当做容器类的元素  (容器类,list,dict,tuple) l = [func,time,obj,1]
    
    
    # 类也是对象,Foo=type(...)
    class Foo:
        pass
    
    obj = Foo()
    print(type(obj))    # obj的类型是Foo 类
    print(type(Foo))   # Foo的类型是type

     

    3、什么是元类?

    • 产生类的类称之为元类,默认所以用class定义的类,他们的元类是type
    元类是类的类,是类的模板
    
    元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为
    
    元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
    
    type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
    

      

    4、定义类的两种方式

      (1)方式一:使用class关键字

    class Chinese:
        country = 'china'
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def talk(self):
            print('%s is talking' % self.name)
    

      (2)方式二:就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建

    # 定义类的三要素:
    # 类名,
    # 类的父类 -->类的基类们,
    # 类体 --->类的名称空间
    

      

    class_name = 'Chinese'    # 类名
    class_bases = (object,)     # 类的基类
    
    # 类体
    class_body = """
    country = 'china'
    
    def __init__(self,name,age):
        self.name = name
        self.age = age
    
    def talk(self):
        print("%s is talking" % self.name)
    """
    
    # 生成类的局部名称空间,即填充字典
    class_dict = {}
    exec(class_body, globals(), class_dict)  # exec(p_object, globals, locals)
    print(class_dict)
    
    # 调用元类type(也可以自定义)来产生类Chinense
    Chinese = type(class_name, class_bases, class_dict)

    #实现类调用
    man = Chinese('alex', 22)
    print(Chinese.__dict__)
    print(man.__dict__)
    
    # 运行结果
    {'__init__': <function __init__ at 0x0023B150>, 'talk': <function talk at 0x01DE3810>, 'country': 'china'}
    {'__doc__': None, 'talk': <function talk at 0x01DE3810>, 'country': 'china', 
    '__dict__': <attribute '__dict__' of 'Chinese' objects>, 
    '__init__': <function __init__ at 0x0023B150>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>,
     '__module__': '__main__'}
    {'age': 22, 'name': 'alex'}
     

    5.自定义元类控制类的行为

      (1)创建类

    一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,
    用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的行为,工作流程是什么)
    

      

    class Mymeta(type):
        def __init__(self, class_name, class_bases, class_dict):
            print(class_name)   # 类名
            print(class_bases)  # 类的基类
            print(class_dict)   # 类的命名空间
    
            super(Mymeta, self).__init__(class_name, class_bases, class_dict)
    
    
    class Chinese(object, metaclass=Mymeta):
        contry = 'china'
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def talk(self):
            print('%s is talking' % self.name)
    Chinese
    (<class 'object'>,)
    {'__module__': '__main__', '__qualname__': 'Chinese', 'talk': <function Chinese.talk at 0x01F33780>, 
    '__init__': <function Chinese.__init__ at 0x01F33738>, 'contry': 'china'}
    

      (2)自定义类的行为

     

    6、控制类的实例化行为

      (1)__call__ 方法

    class Foo(object):
        def __call__(self, *args, **kwargs):
            print(self)
            print(args)
            print(kwargs)
    
    
    # 调用类People,并不会触发__call__
    foo = Foo()
    
    # 调用对象obj(1,2,3,a=1,b=2,c=3),才会出发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3)
    foo(1,2,3,a=1,b=2,c=3)
    
    # #总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,
    # 会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj

      

      (2)元类中的call方法

    • 总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj

       

      (3)自定义元类,控制类的调用(即实例化)的过程

     

       (4)单例模式

    • 基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存
    class Mysql:
        __instance = None  # obj1  # 类属性
    
        def __init__(self):
            self.host = '127.0.0.1'
            self.port = 3306
    
        @classmethod
        def single(cls):
            if not cls.__instance:
                obj = cls()
                cls.__instance = obj
            return cls.__instance
    
        def conn(self):
            pass
    
        def execute(self):
            pass
    
    # obj1 = Mysql()
    # obj2 = Mysql()
    # obj3 = Mysql()
    #
    # print(obj1)
    # print(obj2)     # 3个不同的内存地址
    # print(obj3)       # 不是同一块内存,不是同一个对象
    
    obj1 = Mysql.single()
    obj2 = Mysql.single()
    obj3 = Mysql.single()
    
    print(obj1 is obj2 is obj3) # True

    7.应用:定制元类实现单例模式

    class Mymeta(type):
        def __init__(self, class_name, class_bases, class_dict):  # 定义类Mysql时就触发
    
            if not class_name.istitle():  # #Mysql(...)时触发
                raise TypeError('类名必须大写')
    
            super(Mymeta,self).__init__(class_name, class_bases, class_dict)
            self.__instance = None
    
        def __call__(self, *args, **kwargs):
            if not self.__instance:
                obj = object.__new__(self)     # 产生对象 # new方法只是创建了类对象,没有初始化,没有实例化对象
                self.__init__(obj)      # 初始化对象
                self.__instance = obj
                # 上述两步可以合成下面一步
                # self.__instance=super().__call__(*args,**kwargs)
    
            return self.__instance
    
    
    class Mysql(object,metaclass=Mymeta):
        def __init__(self):
            self.host = '127.0.0.1'
            self.port = 3306
    
        def conn(self):
            pass
    
        def execute(self):
            pass
    
    obj1 = Mysql()
    obj2 = Mysql()
    obj3 = Mysql()
    
    print(obj1 is obj2 is obj3)  # True

    8、 练习题

    练习一:在元类中控制把自定义类的数据属性都变成大写

    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' #龙的传人
        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}
    '''
    View Code

    练习二:在元类中控制自定义的类无需init方法

    1.元类帮其完成创建对象,以及初始化操作;
      2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
      3.key作为用户自定义类产生对象的属性,且所有属性变成大写

    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) #创建对象,self为类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' #龙的传人
        def walk(self):
            print('%s is walking' %self.name)
    
    
    p=Chinese(name='egon',age=18,sex='male')
    print(p.__dict__)
    View Code

     9、单例模式下,高并发,线程安全

    1、问题描述

    kubernete包两年前的版本大概是64的样子,后来不知道什么原因,可能维护的人觉得Configuration对象有很多域都是可供用一个的,弄了个metaclass,目的是当创建这个类的第一个实例后,后续的实例都以第一个实例为基准,也就成了62的样子。 我的代码在使用的时候,每次都回创建一个新的configuration实例

    # _*_ coding : UTF-8 _*_
    # Author : Jack
    """
    File_name : 
    Function : 
    Date : 
    """
    from kubernetes.client import Configuration
    # 创建一个实例化对象a
    # a = Configuration()
    # # 创建一个实例化对象b
    # b = Configuration()
    # # a = Configuration
    # # b = Configuration
    #
    # # a.api_key = {'authorization': "token a"}
    # # 修改父类中init方法中api_key的属性,在初始化init参数时,实例已经存在
    # a.api_key['authorization'] = "token a"
    #
    # # b.api_key = {'authorization': "token b"}
    # # 修改父类中api_key的属性,在初始化init参数,由于初始化时实例已经存在,相当于init共用,这样b的值就覆盖了a的值
    # b.api_key['authorization'] = "token b"
    # # 所以输出结果会是b的值
    # print(a.api_key['authorization'])
    
    a = Configuration()
    a.api_key['authorization'] = "token a"
    print(a.api_key['authorization'])
    b = Configuration()
    b.api_key['authorization'] = "token b"
    print(a.api_key['authorization'])

    那么问题来了, 为什么会偶尔出现401, 也就是说发给集群A的token,实际上属于B

    2、原因

    3、结论

    https://raising.iteye.com/blog/2246125
    https://www.cnblogs.com/Piers/p/9333645.html
    https://meizhi.iteye.com/blog/537563

    多线程下,共享的数据,不安全,
    需要锁






  • 相关阅读:
    pytest测试框架的简单应用
    windows下Docker无法正常启动-The system cannot find the file specified
    docker-centos6.9 /etc/rc.d/init.d/functions: No such file or directory解决方法
    All TAP-Windows adapters on this system are currently in use.
    DNS服务器配置named.ca详解
    python单元测试unittest常用断言
    python学习之异常
    python学习之元类
    python学习之property进阶
    python学习之描述符自制property
  • 原文地址:https://www.cnblogs.com/venicid/p/8617594.html
Copyright © 2020-2023  润新知