zoukankan      html  css  js  c++  java
  • 【转】Python-面向对象进阶

    【转】Python-面向对象进阶

    一、isinstance(obj, cls) and issubclass(sub, super)

    1. isinstance(obj, cls),检查obj是否是类cls的对象

    1 class A:
    2     pass
    3 
    4 obj = A()
    5 print(isinstance(obj, A))
    6 
    7 #运行结果
    8 #True

    2. issubclass(sub, super),检查sub类是否是super类的派生类(子类)

     1 class A:
     2     pass
     3 
     4 class B(A):
     5     pass
     6 
     7 print(issubclass(B, A))
     8 
     9 #运行结果
    10 #True

    二、反射

    1. 什么是反射

    反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

    2. python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)。

     1 class People:
     2     country = 'China'
     3     def __init__(self, name, age):
     4         self.name = name
     5         self.age = age
     6 
     7     def info(self):
     8         print('%s is %d years old' % (self.name, self.age))
     9 
    10 p = People('jack', 18)
    11 
    12 #hasattr(obj, name),检查属性
    13 print(hasattr(p, 'info'))   #检查对象p是否有‘info’属性,结果True
    14 print(hasattr(p, 'name'))   #检查对象p是否有‘name’属性,结果True
    15 
    16 #getattr(obj, name)获取属性
    17 print(getattr(p, 'name'))   #获得对象p的‘name’属性,结果:返回p.name的值,即jack
    18 print(getattr(p, 'info'))   #获得对象p的‘info’属性,结果:返回p.info的值,
    19                             # 即绑定方法info的内存地址:<bound method People.info of <__main__.People object at 0x000001B64317ABE0>>
    20 getattr(p, 'info')()        #由于getattr返回的是对象方法属性的内存地址,加()就可以调用,结果:jack is 18 years old
    21 
    22 #setattr(x, y, v)设置属性
    23 setattr(p, 'age', 21)       #修改对象p的‘age’属性,结果:p.age的值变为21
    24 setattr(p, 'sex', 'male')   #新增对象p的‘sex’属性,结果:p.sex的值为male
    25 print(p.__dict__)           #查看对象p的数据属性,结果:{'name': 'jack', 'age': 21, 'sex': 'male'}
    26 
    27 #delattr(x, y)删除属性
    28 delattr(p, 'sex')           #删除对象p的‘sex’属性
    29 print(p.__dict__)           #结果:{'name': 'jack', 'age': 21}
    四个可以实现自省的函数:hasattr(obj, name);getattr(obj, name);setattr(x, y, v);delattr(x, y)
     1 #类也是对象
     2 class Foo(object):
     3     staticField = "old boy"
     4 
     5     def __init__(self):
     6         self.name = 'wupeiqi'
     7 
     8     def func(self):
     9         return 'func'
    10 
    11     @staticmethod
    12     def bar():
    13         return 'bar'
    14 
    15 
    16 print(getattr(Foo, 'staticField'))  #获取类的'staticField'属性,结果:old boy
    17 
    18 print(getattr(Foo, 'func'))         #获取类的'func'属性,结果:<function Foo.func at 0x0000018156FBB950>
    19 print(getattr(Foo, 'func')('self'))     #加()调用方法,结果:func
    20 
    21 print(getattr(Foo, 'bar'))          #获取类的'bar'属性,结果:<function Foo.bar at 0x00000192D2AFB9D8>
    22 print(getattr(Foo, 'bar')())        #加()调用方法,结果:bar
    类也是对象,能够应用反射
     1 #反射当前模块成员
     2 #!/usr/bin/env python
     3 # -*- coding:utf-8 -*-
     4 
     5 import sys
     6 
     7 
     8 def s1():
     9     print('s1')
    10 
    11 
    12 def s2():
    13     print('s2')
    14 
    15 
    16 this_module = sys.modules[__name__]
    17 
    18 print(this_module)                      #结果:<module '__main__' from '......'>
    19 print(hasattr(this_module, 's1'))       #结果:True
    20 print(getattr(this_module, 's2'))       #结果:<function s2 at 0x0000020590EAB8C8>
    21 getattr(this_module, 's2')()            #结果:s2
    模块也是对象,能够应用反射

    3. 反射的好处

    好处一:实现可插拔机制

    有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。

    总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。

     1 class FtpClient:
     2     'ftp客户端,但是还么有实现具体的功能'
     3     def __init__(self,addr):
     4         print('正在连接服务器[%s]' %addr)
     5         self.addr=addr
     6 
     7 ##############################
     8 #不影响lili的代码编写
     9 
    10 from module import FtpClient
    11 f1=FtpClient('192.168.1.1')
    12 if hasattr(f1,'get'):
    13     func_get=getattr(f1,'get')
    14     func_get()
    15 else:
    16     print('---->不存在此方法')
    17     print('处理其他的逻辑')

    好处二:动态导入模块(基于反射当前模块成员)

     1 #两种导入用户输入模块得方法,官方推荐方法2
     2 #方法1
     3 m = input('input your module:')     #用户输入要导入的模块名,以time模块为例
     4 m1 = __import__(m)
     5 print(m1)                           #结果:<module 'time' (built-in)>
     6 print(m1.time())                    #结果:1493023753.0157707,当前时间
     7 
     8 #方法2
     9 import importlib                    #先导入importlib模块
    10 t = importlib.import_module(m)
    11 print(t)                            #结果:<module 'time' (built-in)>
    12 print(t.time())                     #结果:1493023753.0238242,当前时间

    三、内置attr

     1 class Foo:
     2     x = 1
     3     def __init__(self, y):
     4         self.y = y
     5 
     6     def __getattr__(self, item):
     7         print('----> from getattr:你找的属性不存在')
     8 
     9     def __setattr__(self, key, value):
    10         print('----> from setattr')
    11         # self.key=value                    #这就无限递归了
    12         self.__dict__[key] = value          #应该使用它
    13 
    14     def __delattr__(self, item):
    15         print('----> from delattr')
    16         # del self.item                     #无限递归了
    17         self.__dict__.pop(item)             #应该使用它
    18 
    19 #__setattr__添加/修改属性会触发它的执行
    20 f1 = Foo(10)                #因为重写了__setattr__,凡是赋值操作都会触发它的运行
    21 print(f1.__dict__)          #结果:----> from setattr {'y': 10}
    22 f1.z = 3                    #添加属性
    23 print(f1.__dict__)          #结果:----> from setattr {'y': 10, 'z': 3}
    24 
    25 #__delattr__删除属性的时候会触发
    26 f1.__dict__['a'] = 3        #我们可以直接修改属性字典,来完成添加/修改属性的操作
    27 del f1.a                    #触发__delattr__
    28 print(f1.__dict__)          #结果:----> from delattr {'y': 10, 'z': 3}
    29 
    30 #__getattr__只有在使用对象调用属性且属性不存在的时候才会触发
    31 print(f1.y)                 #属性存在,结果:10
    32 f1.a                        #属性a不存在,触发__getattr__,结果:----> from getattr:你找的属性不存在

    四、二次加工标准类型(包装)

    包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

     1 #二次加工标准类型(基于继承实现)
     2 class List(list):               #继承list所有的属性,也可以派生出自己新的,比如append和mid
     3     def append(self, p_object):
     4         ' 派生自己的append:加上类型检查'
     5         if not isinstance(p_object, int):
     6             raise TypeError('must be int')
     7         super().append(p_object)
     8 
     9     @property
    10     def mid(self):
    11         '新增自己的属性'
    12         index = len(self)//2
    13         return self[index]
    14 
    15 l = List([1, 2, 3, 4])
    16 print(l)
    17 l.append(5)
    18 print(l)                        #结果:[1, 2, 3, 4, 5]
    19 # l.append('1111111')           #报错,必须为int类型
    20 
    21 print(l.mid)                    #结果:3
    22 
    23 #其余的方法都继承list的
    24 l.insert(0, -123)               #插入元素
    25 print(l)                        #结果:[-123, 1, 2, 3, 4, 5]
    26 l.clear()                       #清空列表
    27 print(l)                        #结果:[]

    授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

    实现授权的关键点就是覆盖__getattr__方法

     1 # 授权示范
     2 import time
     3 
     4 
     5 class FileHandle:
     6     def __init__(self, filename, mode='r', encoding='utf-8'):
     7         self.file = open(filename, mode, encoding=encoding)         #获得文件句柄
     8 
     9     def write(self, line):      #重新定义write方法,新增添加时间的功能
    10         t = time.strftime('%Y-%m-%d %T')
    11         self.file.write('%s %s' % (t, line))
    12 
    13     def __getattr__(self, item):    #文件操作的其它属性在FileHandle类中找不到时,触发__getattr__
    14         return getattr(self.file, item)
    15 
    16 
    17 f1 = FileHandle('b.txt', 'w+')      #新建文件b.txt,获得文件句柄,赋给对象f1
    18 f1.write('你好啊')                 #调用类中的定制方法write
    19 f1.seek(0)                         #重置文件位置于文首,触发__getattr__,正常调用
    20 print(f1.read())                   #打印文件内容,触发__getattr__,正常调用,结果:2017-04-24 17:30:37 你好啊
    21 f1.close()                         #关闭文件,触发__getattr__,正常调用

    五、__getattribute__

    __getattribute__是访问属性的方法,我们可以通过方法重写来扩展方法的功能。

    当获取属性时,直接return object.__getattribute__(self, *args, **kwargs)

    如果需要获取某个方法的返回值时,则需要在函数后面加上一个()即可。如果不加的话,返回的是函数引用地址。

     1 class Foo:
     2     def __init__(self, x):
     3         self.x = x
     4         self.y = 100
     5 
     6     def __getattr__(self, item):      #属性不存在时触发执行
     7         print('getattr')
     8         if item == 'y':
     9             return 'y = 100'
    10         else:
    11             return "No %s attribute" %item
    12 
    13     def __getattribute__(self, item):       #属性存不存在都会触发执行,而且当与__getattr__同时存在时,仅执行自己,除非抛出错误后,会执行__getattr__
    14         print('__getattribute__ is called')
    15         if item == 'x':
    16             return 'x = %s' %(object.__getattribute__(self, item))      #返回属性
    17         else:
    18             raise AttributeError("No 'x' attribute")        #当抛出错误时,会去执行__getattr__
    19 
    20 
    21 f = Foo(10)
    22 print(f.x)      #object存在‘x’属性,运行结果:__getattribute__ is called x = 10
    23 print(f.y)      #object存在‘y’属性,触发__getattribute__,不符合if条件,抛出错误,会去执行__getattr__
    24                 #运行结果:__getattribute__ is called,getattr,y = 100
    25 print(f.z)      #object不存在‘z’属性,但还会触发_getattribute__,抛出错误,触发__getattr__,返回return的值
    26                 #运行结果:__getattribute__ is called,getattr,No z attribute

    六、__setitem__,__getitem,__delitem__

    触发机制与attr一致,只是将对象操作属性模拟为字典的格式:

     1 # 把对象操作属性模拟成字典的格式
     2 class Foo:
     3     def __init__(self, name):
     4         self.name = name
     5 
     6     def __getitem__(self, item):
     7         return self.__dict__[item]
     8 
     9     def __setitem__(self, key, value):
    10         self.__dict__[key] = value
    11 
    12     def __delitem__(self, key):
    13         self.__dict__.pop(key)
    14 
    15 
    16 f = Foo('egon')     #实例化
    17 
    18 print(f.name)       #.的方式调用,运行结果:egon
    19 print(f['name'])    #字典方式调用,运行结果:egon
    20 
    21 f.age1 = 18         
    22 f['age'] = 21
    23 print(f.__dict__)   #运行结果:{'name': 'egon', 'age1': 18, 'age': 21}
    24 
    25 del f['age1']
    26 del f.age
    27 print(f.__dict__)   #运行结果:{'name': 'egon'}
    把对象操作属性模拟成字典的格式

    七、__str__,__repr__,__format__

    改变对象的字符串显示__str__,__repr__;自定制格式化字符串__format__

     1 # _*_coding:utf-8_*_
     2 __author__ = 'Linhaifeng'
     3 format_dict = {
     4     'nat': '{obj.name}-{obj.addr}-{obj.type}',  # 学校名-学校地址-学校类型
     5     'tna': '{obj.type}:{obj.name}:{obj.addr}',  # 学校类型:学校名:学校地址
     6     'tan': '{obj.type}/{obj.addr}/{obj.name}',  # 学校类型/学校地址/学校名
     7 }
     8 
     9 
    10 class School:
    11     def __init__(self, name, addr, type):
    12         self.name = name
    13         self.addr = addr
    14         self.type = type
    15 
    16     def __repr__(self):
    17         return 'School(%s,%s)' % (self.name, self.addr)
    18 
    19     def __str__(self):
    20         return '(%s,%s)' % (self.name, self.addr)
    21 
    22     def __format__(self, format_spec):
    23         if not format_spec or format_spec not in format_dict:
    24             format_spec = 'nat'
    25         fmt = format_dict[format_spec]
    26         return fmt.format(obj=self)
    27 
    28 
    29 s1 = School('oldboy1', '北京', '私立')
    30 print('from repr: ', repr(s1))  #运行结果:from repr:  School(oldboy1,北京)
    31 print('from str: ', str(s1))    #运行结果:from str:  (oldboy1,北京)
    32 print(s1)                       #默认以‘str’定义的方式输出;运行结果:(oldboy1,北京)
    33 
    34 '''
    35 str函数或者print函数--->obj.__str__()
    36 repr或者交互式解释器--->obj.__repr__()
    37 如果__str__没有被定义,那么就会使用__repr__来代替输出
    38 注意:这俩方法的返回值必须是字符串,否则抛出异常
    39 '''
    40 print(format(s1, 'nat'))    #指定格式‘nat’输出;运行结果:oldboy1-北京-私立
    41 print(format(s1, 'tna'))    #指定格式‘tna’输出;运行结果:私立:oldboy1:北京
    42 print(format(s1, 'tan'))    #指定格式‘tan’输出;运行结果:私立/北京/oldboy1
    43 print(format(s1, 'asfdasdffd'))     #其它情况默认以‘nat’输出;运行结果:oldboy1-北京-私立
    自定义格式化输出实例

    八、__slots__

    正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。

    但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加nameage属性。为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

     1 '''
     2 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
     3 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
     4 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
     5 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
     6 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
     7 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
     8 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
     9 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
    10 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。           更多的是用来作为一个内存优化工具。
    11 
    12 '''
    13 
    14 class Student(object):
    15     __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
    16 
    17 s = Student()       # 创建新的实例
    18 s.name = 'jack'     # 绑定属性'name'
    19 s.age = 21          # 绑定属性'age'
    20 #s.score = 99        # 绑定属性'score';报错:'Student' object has no attribute 'score'
    21 
    22 #使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
    23 class G(Student):
    24     pass
    25 
    26 g = G()             # 创建新的实例
    27 g.name = 'jack'     # 绑定属性'name'
    28 g.age = 21          # 绑定属性'age'
    29 g.score = 99        # 绑定属性'score'
    限制实例的绑定属性,当前类有效

    九、__next__和__iter__实现迭代器协议

    如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

    我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

     1 class Fib(object):
     2     def __init__(self):
     3         self.a, self.b = 0, 1       # 初始化两个计数器a,b
     4 
     5     def __iter__(self):
     6         return self                 # 实例本身就是迭代对象,故返回自己
     7 
     8     def __next__(self):
     9         self.a, self.b = self.b, self.a + self.b        # 计算下一个值
    10         if self.a > 100:         # 退出循环的条件
    11             raise StopIteration()
    12         return self.a               # 返回下一个值
    13 
    14 for i in Fib():
    15     print(i)
    斐波那契数列

    十、__doc__

    返回对象的描述信息

    class Foo:
        """描述信息"""
        pass
    
    print(Foo.__doc__)      #运行结果:描述信息
    
    #__doc__不能被继承
    class Bar(Foo):
        pass
    
    print(Bar.__doc__)      #运行结果:None

    十一、__module__和__class__

    __module__ 表示当前操作的对象在哪个模块;__class__ 表示当前操作的对象的类是什么

    1 #!/usr/bin/env python
    2 # -*- coding:utf-8 -*-
    3 #将该文件存为 a.py
    4 
    5 class C:
    6 
    7     def __init__(self):
    8         self.name = 'a.py'
    #与a.py同一目录下创建新文件
    
    from a import C     #从a模块导入类C
    
    obj = C()
    print(obj.__module__)         # 输出 a,即:输出模块
    print(obj.__class__)            # 输出 <class 'a.C'>,即:输出类

    十二、__del__

    析构方法,当对象在内存中被释放时,自动触发执行。

    注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

    class Foo:
    
        def __del__(self):
            print('执行我啦')
    
    f1=Foo()
    del f1                     #触发__del__
    print('------->')
    
    #输出结果
    执行我啦
    ------->
    
    #########################
    
    class Foo:
    
        def __del__(self):
            print('执行我啦')
    
    f1=Foo()
    # del f1
    print('------->')
    
    #输出结果
    ------->
    执行我啦        #对于当前程序,由于print('------->')运行完后程序就结束了,在结束前,会自动触发__del__

    十三、__enter__和__exit__

    我们知道在操作文件对象的时候可以这么写

    1 with open('a.txt') as f:
    2    '代码块'

    上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

     1 class Open:
     2     def __init__(self, name):
     3         self.name = name
     4 
     5     def __enter__(self):
     6         print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
     7         # return self
     8 
     9     def __exit__(self, exc_type, exc_val, exc_tb):
    10         print('with中代码块执行完毕后执行__exit__')
    11 
    12 
    13 with Open('a.txt') as f:
    14     print('=====>执行代码块')
    15 
    16 
    17 #运行结果:
    18 """
    19 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
    20 =====>执行代码块
    21 with中代码块执行完毕后执行__exit__
    22 """
    上下文管理协议

    __exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

     1 class Open:
     2     def __init__(self, name):
     3         self.name = name
     4 
     5     def __enter__(self):
     6         print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
     7         # return self
     8 
     9     def __exit__(self, exc_type, exc_val, exc_tb):
    10         print('with中代码块执行完毕后执行__exit__')
    11         print(exc_type)
    12         print(exc_val)
    13         print(exc_tb)
    14         #return True
    15 
    16 with Open('a.txt') as f:
    17     print('=====>执行代码块')
    18     raise AttributeError('手动抛错')
    19 
    20 print('其它内容')             #不会执行
    21 
    22 
    23 #运行结果:
    24 """
    25 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
    26     raise AttributeError('手动抛错')
    27 =====>执行代码块
    28 with中代码块执行完毕后执行__exit__
    29 <class 'AttributeError'>
    30 手动抛错
    31 AttributeError: 手动抛错
    32 <traceback object at 0x0000027B163221C8>
    33 """
    with语句抛错,后面语句不执行

    如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

     1 class Open:
     2     def __init__(self, name):
     3         self.name = name
     4 
     5     def __enter__(self):
     6         print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
     7         # return self
     8 
     9     def __exit__(self, exc_type, exc_val, exc_tb):
    10         print('with中代码块执行完毕后执行__exit__')
    11         print(exc_type)
    12         print(exc_val)
    13         print(exc_tb)
    14         return True
    15 
    16 with Open('a.txt') as f:
    17     print('=====>执行代码块')
    18     raise AttributeError('手动抛错')
    19 
    20 print('其它内容')
    21 #运行结果:
    22 """
    23 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
    24 =====>执行代码块
    25 with中代码块执行完毕后执行__exit__
    26 <class 'AttributeError'>
    27 手动抛错
    28 <traceback object at 0x000001CB500921C8>
    29 其它内容
    30 """
    __exit__返回True,with后语句正常执行

    用途或者说好处:

    1. 使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
    2. 在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关心这个问题,这将大有用处

    十四、__call__

    对象后面加括号,触发执行。

    注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

     1 class Foo:
     2     def __init__(self):
     3         pass
     4 
     5     def __call__(self, *args, **kwargs):
     6         print('__call__')
     7 
     8 
     9 obj = Foo()     # 执行 __init__
    10 obj()           # 执行 __call__
    11 Foo()()         # 执行 __call__
    obj()触发__call__

    十五、metaclass

    1. 引子

    1 class Foo:
    2      pass
    3 
    4 f1=Foo()        #f1是通过Foo类实例化的对象

    python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)

    上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

    1 #type函数可以查看类型,也可以用来查看对象的类,二者是一样的
    2 print(type(f1))         # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建
    3 print(type(Foo))        # 输出:<class 'type'>  

    2. 什么是元类?

    元类是类的类,是类的模板

    元类是用来控制如何创建类的,正如类是创建对象的模板一样

    元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)

    type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

    3. 创建类的两种方式

    方式一:

    1 class Foo:
    2      def func(self):
    3          print('from func')

    方式二:

    1 def func(self):
    2         print('from func')
    3 
    4 x=1
    5 Foo=type('Foo',(object,),{'func':func,'x':1})
    6 
    7 # type(object_or_name, bases, dict)

    4. 自定义元类

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

     当创建一个元类的对象(也是类)时的流程:

     1 class Mymeta(type):
     2     def __init__(self, name, bases, dic):       #5 执行__init__函数,初始化对象
     3         print('===>Mymeta.__init__')            #6 打印'===>Mymeta.__init__'
     4 
     5     def __new__(cls, *args, **kwargs):      #2 执行__new__函数,创建对象
     6          print('===>Mymeta.__new__')        #3 打印'===>Mymeta.__new__'
     7          return type.__new__(cls, *args, **kwargs)      #4 返回类Mymeta创建的对象Foo
     8 
     9 class Foo(object,metaclass=Mymeta):     #1 类Foo是元类Mymeta的对象,在创建对象时,会先后调用类的__new__和__init__方法
    10     def __init__(self, name):
    11          self.name = name
    12     def __new__(cls, *args, **kwargs):
    13          return object.__new__(cls)
    14 
    15 
    16 
    17 #运行结果:
    18 # ===>Mymeta.__new__
    19 # ===>Mymeta.__init__
    创建元类的对象的流程,只调用元类的new和init

    重点:当通过一个类创建一个对象的时候,会先后调用类的__new__和__init__方法。元类是用来创建类的,也就是说类是元类创建的对象,所以元类的__new__和__init__方法会被调用。

     当创建一个元类的子类的对象(实例)时的流程:

     1 #元类
     2 class MyType(type):
     3 
     4     def __init__(self, class_name, bases=None, dict=None):      #5 执行__init__函数,初始化对象
     5         print('MyType init --->')                                #6 打印:'MyType init --->'
     6         print(classmethod, type(class_name))                     #7 打印:<class 'classmethod'> <class 'str'>
     7         print(bases)                                             #8 打印(Foo的bases是object):(<class 'object'>,);
     8         print(dict)                                              #9 打印:对象Foo的命名空间
     9 
    10 
    11     def __new__(cls, *args, **kwargs):      #2 执行__new__(MyType, *args, **kwargs)函数,创建对象;
    12          print('===>Mymeta new')        #3 打印:===>Mymeta new
    13          return type.__new__(cls, *args, **kwargs)      #4 返回类Mymeta创建的对象Foo
    14 
    15     def __call__(self, *args, **kwargs):        #11 执行__call__(Foo, *args, **kwargs)
    16         print('MyType call --->', self, args, kwargs)       #12 打印:MyType call ---> <class '__main__.Foo'> ('name',) {}
    17         return type.__call__(self, *args, **kwargs)        #13 返回由元类MyType创建好的对象Foo,此时,就要调用Foo的__new__和__init__
    18 
    19 
    20 class Foo(object, metaclass=MyType):     #1 创建元类的对象Foo,触发元类MyType的__new__和__init__
    21     x = 111
    22     def __init__(self, name):       #17 执行:__init__(Foo.obj, 'jack')
    23         print('Foo init')           #18 打印:Foo init
    24         self.name = name            #19 赋值:self.name = 'jack'
    25 
    26     def __new__(cls, *args, **kwargs):      #14 执行:__new__(cls, *args, **kwargs)
    27         print('Foo new')                    #15 打印:Foo new
    28         return object.__new__(cls)          #16 返回由类Foo创建好的对象
    29 
    30 f = Foo('jack')         #10 到这一步,Foo对象已经创建好了,执行Foo('name'),相当于调用父类即MyType里的__call__方法,创建了Foo的对象,然后赋值给f
    31 print(f.name)           #20 打印:jack
    32 
    33 #运行结果:
    34 # ===>Mymeta new
    35 # MyType init --->
    36 # <class 'classmethod'> <class 'str'>
    37 # (<class 'object'>,)
    38 # {'__module__': '__main__', '__qualname__': 'Foo', 'x': 111, '__init__': <function Foo.__init__ at 0x000001F945B748C8>, '__new__': <function Foo.__new__ at 0x000001F945B74950>}
    39 # MyType call ---> <class '__main__.Foo'> ('jack',) {}
    40 # Foo new
    41 # Foo init
    42 # jack
    创建元类的对象的对象,对象名()相当于调用父类的call方法

    补充:

    对于元类的查找,Python有一套规则:

    1. Python解释器会在当前类中查找"__metaclass__"属性对于的代码,然后创建一个类对象
    2. 如果没有找到"__metaclass__"属性,会继续在父类中寻找"__metaclass__属性",并尝试前面同样的操作
    3. 如果在任何父类中都找不到"__metaclass__",就会用内置的type来创建这个类对象

    参考资料:

    1. http://www.cnblogs.com/linhaifeng/articles/6204014.html

    2. http://bbs.csdn.net/topics/360135494

    3. http://www.cnblogs.com/wilber2013/p/4695836.html

  • 相关阅读:
    对称加密和非对称加密
    数字签名
    内存溢出和内存泄漏
    生产随机字符串
    天才高中生参与斯坦福新研究:在图像压缩上,人类比算法强
    用机器人替代宇航员!日本打造远程操作机器人
    机器人也能拥有“物理直觉”?谷歌这款机器人真是厉害了
    35所高校新设人工智能本科专业 专家:人工智能非孤立专业
    “入职1年,我和做 AI 的同学薪资翻了 2 翻!”
    让机器学会认脸究竟有多少用处
  • 原文地址:https://www.cnblogs.com/langqi250/p/10070378.html
Copyright © 2011-2022 走看看