• 元类


    阅读目录

    知识储备

    知识储备
    1 code = {
    2 字符串
    3 }
    4 
    5 exec(code,global_dic,local_dic),
    一、其中的字符串内,默认为局部空间如下例:
     1 code="""
     2 global x
     3 x=1
     4 y=2
     5 """
     6 global_dic={'x':10000000}
     7 local_dic={}
     8 exec(code,global_dic,local_dic)
     9 
    10 print(global_dic) # {'x':1,...}
    11 print(local_dic) # {'y':2}

    二、产生类有两个方式:

    1、一种是用class关键字(背后原理就是调用type)直接生成类:
      
     1 # Chinese = type(...)
     2 class Chinese: # class关键字自动拿到类名,括号内的基类,自动调exec函数的执行,把类体代码执行一遍得到一个字典,凑齐三要素自动赋值给type
     3     country = 'China'
     4 
     5     def __init__(self, name, age, sex):
     6         self.name = name
     7         self.age = age
     8         self.sex = sex
     9 
    10     def speak(self):
    11         print('%s speak Chinese' % self.name)
    12 
    13 print(Chinese) # <class '__main__.Chinese'>

      我们用class定义的类来实例化出对象供我们使用,而内置元类type是专门来产生class定义的类
     1 # 三大要素
     2 # 类名,类的基类,类的局部名称空间
     3 class_name = 'Chinese'
     4 class_bases = (object,)
     5 class_body = """
     6 country = 'China'
     7 def __init__(self,name,age,sex):
     8     self.name = name
     9     self.age = age
    10     self.sex = sex
    11 
    12 def speak(self):
    13     print('%s speak Chinese' % self.name)
    14 """
    15 class_dic = {}
    16 exec(class_body,{},class_dic)
    17 
    18 # 类的三大要素
    19 # print(class_name,class_bases,class_dic)
    20 
    21 obj = type(class_name,class_bases,class_dic)
    22 print(obj) #<class '__main__.Chinese'>
    2、另一种就是自定义元类,再由该元类定制类:
    1 #上一步骤中,将最后type实例化的对象名修改成‘Chinese’,即可得到最终结果:
    2 Chinese = type(class_name,class_bases,class_dic)
    3 print(Chinese) #<class '__main__.Chinese'>
    4 lmj = Chinese('lmj',18,'male')
    5 print(lmj.name,lmj.age,lmj.sex) #lmj 18 male
    三、__call__,调用条件下,会自动触发

    class Foo:
        def __init__(self):
            pass
    
        def __str__(self):
            pass
    
        def __del__(self):
            pass
    
        def __call__(self, *args, **kwargs):
            print('run',args,kwargs)
    
    obj = Foo()
    obj(1,2,3,a=1,b=2,c=3) # run (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}

    注意:调用对象,则会自动将对象所在类的下的绑定方法__call__的执行,然后将对象本身当作第一个参数传给self,将调用对象时括号内的值传给*args与**kwargs。

     

    正题:

    类的类是元类(默认type),元类内也必定有__call__方法,用来在调用元类生成类的时候。

    当一个类指定他的元类是自定义的(MyMeta),那么就可以通过这个__call__方法来控制自定义化。

    自定义类,控制创建类的行为:

     1 # 自定义元类
     2 # 控制创建类的行为
     3 class Mymeta(type):
     4     # self = Foo
     5     def __init__(self,class_name,class_bases,class_dic):
     6         print(class_name) # Foo
     7         print(class_bases) # (<class 'object'>,)
     8         print(class_dic) #{'__module__': '__main__', '__qualname__': 'Foo', 'x': 1, '__init__': <function Foo.__init__ at 0x000000ABF6FD9C80>, 'f1': <function Foo.f1 at 0x000000ABF6FD9D08>}
     9 
    10 # Foo = Mymeta('Foo',(object,),class_dic) 此处可理解为Mymeta类的实例化
    11 class Foo(object,metaclass=Mymeta):
    12     x = 1
    13     def __init__(self,y):
    14         self.y = y
    15 
    16     def f1(self):
    17         print('from f1')
    控制创建类

    进阶完整版如下:

     1 # 自定义元类
     2 # 控制创建类的行为
     3 class Mymeta(type):
     4     # self = Foo
     5     def __init__(self,class_name,class_bases,class_dic):
     6         # print(class_name)
     7         # print(class_bases)
     8         # print(class_dic)
     9 
    10         if not class_name.istitle():
    11             raise TypeError('类名首字母必须大写')
    12         # 重用父类__init__功能
    13 
    14         if not class_dic.get('__doc__'):
    15             raise TypeError('类中必须有文档注释')
    16 
    17         super(Mymeta,self).__init__(class_name,class_bases,class_dic)
    18 
    19 
    20 
    21 
    22 # Foo = Mymeta('Foo',(object,),class_dic) 此处可理解为Mymeta类的实例化
    23 class Foo(object,metaclass=Mymeta):
    24     """
    25     文档注释
    26     """
    27     x = 1
    28     def __init__(self,y):
    29         self.y = y
    30 
    31     def f1(self):
    32         print('from f1')
    View Code

    自定义类,控制调用类:

     注意:不能用init方法,因为new是在init之前执行的,当new了以后,字典就创建好了,再调init就已经改变不了字典里的内容了,如下:

    因此,正确的打开方式如下,使用__new__方法:

     1 class Mymeta(type):
     2     # self = Foo
     3     # 来控制类Foo的创建
     4     def __new__(self,class_name,class_bases,class_dic):
     5         update_attrs = {}
     6         for k,v in class_dic.items():
     7             if not callable(v) and not k.startswith('__'): # 非可调用的值(非函数)和非内置属性
     8                 update_attrs[k.upper()] = v
     9             else:
    10                 update_attrs[k] = v
    11 
    12 
    13         if not class_name.istitle():
    14             raise TypeError('类名首字母必须大写')
    15         # 重用父类__init__功能
    16 
    17         if not class_dic.get('__doc__'):
    18             raise TypeError('类中必须有文档注释')
    19 
    20         return type.__new__(self,class_name,class_bases,update_attrs)
    21 
    22 
    23     # 控制类Foo的调用,即控制实例化Foo的过程
    24     def __call__(self, *args, **kwargs): #self=Foo,args=(111,)
    25         # print('+++>')
    26         # return 123
    27 
    28         # 1 造空对象obj
    29         obj = object.__new__(self)
    30         # 2 调用Foo.__init__,将obj连同调用Foo括号内的参数一同传给__init__方法
    31         self.__init__(obj, *args, **kwargs)
    32         return obj
    33 
    34 # Foo = Mymeta('Foo',(object,),class_dic) 此处可理解为Mymeta类的实例化
    35 class Foo(object,metaclass=Mymeta):
    36     """
    37     文档注释
    38     """
    39     tag = 'China'
    40     x = 1
    41     def __init__(self,y):
    42         self.y = y
    43 
    44     def f1(self):
    45         print('from f1')
    46 
    47 # 只要调用类,必产生空对象
    48 obj = Foo(111) #此步会触发Foo对象所在类(此例即为Mymeta)之下的__call__()
    49 
    50 
    51 print(Foo.__dict__)
    52 """
    53 {'__module__': '__main__', '__doc__': '
        文档注释
        ', 'TAG': 'China', 'X': 1, '__init__': <function Foo.__init__ at 0x000000A0F3269D08>, 'f1': <function Foo.f1 at 0x000000A0F3269D90>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>}
    __new__实现类属性全大写
  • 相关阅读:
    CentOS7安装iptables防火墙
    Linux下ntpdate时间同步
    linux下的时间及时区设置
    linux下的DNS
    sysctl
    ab -n -c
    VIM 中 查看{}是否闭合,按%跳转到下个闭合
    要删除共享的初始登陆名 cmd下输入net use * /delete
    case in esac ` for in do done ` while true / false
    read op case $op in
  • 原文地址:https://www.cnblogs.com/limengjie0104/p/8871704.html
Copyright © 2020-2023  润新知