Day 33(07/07)反射

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day 33(07/07)反射相关的知识,希望对你有一定的参考价值。

一 isinstance(obj,cls)和issubclass(sub,super)

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

1 class Foo(object):
2     pass
3  
4 obj = Foo()
5  
6 isinstance(obj, Foo)

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

技术分享
1 class Foo(object):
2     pass
3  
4 class Bar(Foo):
5     pass
6  
7 issubclass(Bar, Foo)
技术分享

二 反射

1 什么是反射

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

 

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

四个可以实现自省的函数

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

技术分享 hasattr(object,name)
技术分享 getattr(object, name, default=None)
技术分享 setattr(x, y, v) 
技术分享 delattr(x, y) 
技术分享 四个方法的使用演示
技术分享 类也是对象
技术分享 反射当前模块成员

导入其他模块,利用反射查找该模块是否存在某个方法

技术分享 module_test.py 
技术分享
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3  
 4 """
 5 程序目录:
 6     module_test.py
 7     index.py
 8  
 9 当前文件:
10     index.py
11 """
12 
13 import module_test as obj
14 
15 #obj.test()
16 
17 print(hasattr(obj,‘test‘))
18 
19 getattr(obj,‘test‘)()
技术分享

 

3 为什么用反射之反射的好处

好处一:实现可插拔机制

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

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

技术分享 egon还没有实现全部功能 
技术分享 不影响lili的代码编写

 

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

技术分享 

 

三 __setattr__,__delattr__,__getattr__

技术分享 三者的用法演示

 

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

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

技术分享 二次加工标准类型(基于继承实现) 

 

技术分享 练习(clear加权限限制)

 

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

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

技术分享 授权示范一
技术分享 授权示范二

 

技术分享 练习题(授权)

 

五 __getattribute__

技术分享 回顾__getattr__
技术分享 __getattribute__
技术分享 二者同时出现

 

六 描述符(__get__,__set__,__delete__)

1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

技术分享 定义一个描述符

2 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

技术分享 引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行
技术分享 描述符应用之何时?何地?

3 描述符分两种
一 数据描述符:至少实现了__get__()和__set__()

1 class Foo:
2     def __set__(self, instance, value):
3         print(‘set‘)
4     def __get__(self, instance, owner):
5         print(‘get‘)

二 非数据描述符:没有实现__set__()

1 class Foo:
2     def __get__(self, instance, owner):
3         print(‘get‘)

 

4 注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()

技术分享 类属性>数据描述符
技术分享 数据描述符>实例属性
技术分享 实例属性>非数据描述符 
技术分享 再次验证:实例属性>非数据描述符
技术分享 非数据描述符>找不到

5 描述符使用

众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能

技术分享 牛刀小试
技术分享 拔刀相助
技术分享 磨刀霍霍
技术分享 大刀阔斧

大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,low,这时候我需要教你一招:独孤九剑

技术分享 类的装饰器:无参
技术分享 类的装饰器:有参

终极大招

技术分享 刀光剑影

6 描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

7 利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

技术分享 @property回顾
技术分享 自己做一个@property
技术分享 实现延迟计算功能
技术分享 一个小的改动,延迟计算的美梦就破碎了 

8 利用描述符原理完成一个自定制@classmethod

技术分享 自己做一个@classmethod

9 利用描述符原理完成一个自定制的@staticmethod

技术分享 自己做一个@staticmethod 

六 再看property

一个静态属性property本质就是实现了get,set,delete三种方法

技术分享 用法一
技术分享 用法二

怎么用?

技术分享 案例一
技术分享 案例二

七 __setitem__,__getitem,__delitem__

技术分享 View Code

 

八 __str__,__repr__,__format__

改变对象的字符串显示__str__,__repr__

自定制格式化字符串__format__

技术分享 View Code 
技术分享 自定义format练习 
技术分享 issubclass和isinstance

 

九 __slots__

技术分享 __slots__使用 
技术分享 刨根问底

 

十 __next__和__iter__实现迭代器协议

技术分享 简单示范

 

 

技术分享
class Foo:
    def __init__(self,start,stop):
        self.num=start
        self.stop=stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.num >= self.stop:
            raise StopIteration
        n=self.num
        self.num+=1
        return n

f=Foo(1,5)
from collections import Iterable,Iterator
print(isinstance(f,Iterator))

for i in Foo(1,5):
    print(i) 
技术分享
技术分享 练习:简单模拟range,加上步长

 

 

技术分享 斐波那契数列

十一 __doc__

技术分享 它类的描述信息

 

技术分享 该属性无法被继承

 

十二 __module__和__class__

  __module__ 表示当前操作的对象在那个模块

  __class__     表示当前操作的对象的类是什么

技术分享 lib/aa.py 
技术分享 index.py

 

十三  __del__

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

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

技术分享 简单示范
技术分享 挖坑埋了你

 

十四 __enter__和__exit__

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

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

 

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

技术分享 上下文管理协议

 

 

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

技术分享 View Code

 

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

技术分享 View Code

 

技术分享 练习:模拟Open

 

用途或者说好处:

1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处

十五 __call__

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

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

技术分享 View Code

 

十六 metaclass

exec:三个参数

参数一:字符串形式的命令

参数二:全局作用域

参数三:局部作用域

exec会在指定的局部作用域内执行字符串内的代码,除非明确地使用global关键字

技术分享 知识储备exec命令

  

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)) # 输出:<type ‘type‘>  

 

2 什么是元类?

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

元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为

元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例Foo类是 type 类的一个实例)

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

技术分享

 

3 创建类的两种方式

方式一:使用class关键字

 

技术分享
class Chinese(object):
    country=‘China‘
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print(‘%s is talking‘ %self.name)
技术分享

 

 

 

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

准备工作:

创建类主要分为三部分

  1 类名

  2 类的父类

  3 类体

技术分享
#类名
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)
"""
技术分享

 

步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

技术分享
class_dic={}
exec(class_body,globals(),class_dic)


print(class_dic)
#{‘country‘: ‘China‘, ‘talk‘: <function talk at 0x101a560c8>, ‘__init__‘: <function __init__ at 0x101a56668>}
技术分享

 

步骤二:调用元类type(也可以自定义)来产生类Chinense

技术分享
Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo


print(Foo) print(type(Foo)) print(isinstance(Foo,type)) ‘‘‘ <class ‘__main__.Chinese‘> <class ‘type‘> True ‘‘‘
技术分享

 

我们看到,type 接收三个参数:

  • 第 1 个参数是字符串 ‘Foo’,表示类名

  • 第 2 个参数是元组 (object, ),表示所有的父类

  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type(‘Foo‘,(Bar,),{})

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

 

所以类实例化的流程都一样,与三个方法有关:(大前提,任何名字后加括号,都是在调用一个功能,触发一个函数的执行,得到一个返回值)

1.obj=Foo(),会调用产生Foo的类内的__call__方法,Foo()的结果即__call__的结果
2.调用__call__方法的过程中,先调用Foo.__new__,得到obj,即实例化的对象,但是还没有初始化
3.调用__call__方法的过程中,如果Foo.__new__()返回了obj,再调用Foo.__init__,将obj传入,进行初始化(否则不调用Foo.__init__)
 
    总结:
__new__更像是其他语言中的构造函数,必须有返回值,返回值就实例化的对象
__init__只是初始化函数,必须没有返回值,仅仅只是初始化功能,并不能new创建对象

 

前提注意:

1. 在我们自定义的元类内,__new__方法在产生obj时用type.__new__(cls,*args,**kwargs),用object.__new__(cls)抛出异常:TypeError: object.__new__(Mymeta) is not safe, use type.__new__()

2. 在我们自定义的类内,__new__方法在产生obj时用object.__new__(self)

元类控制创建类:

技术分享
class Mymeta(type):
    def __init__(self):
        print(‘__init__‘)

    def __new__(cls, *args, **kwargs):
        print(‘__new__‘)

    def __call__(self, *args, **kwargs):
        print(‘__call__‘)

class Foo(metaclass=Mymeta):
    pass

print(Foo)
‘‘‘
打印结果:
__new__
None
‘‘‘

‘‘‘
分析Foo的产生过程,即Foo=Mymeta(),会触发产生Mymeta的类内的__call__,即元类的__call__:
    Mymeta加括号,会触发父类的__call__,即type.__call__
    在type.__call__里会调用Foo.__new__
    而Foo.__new__内只是打印操作,没有返回值,因而Mymeta的结果为None,即Foo=None
‘‘‘
技术分享
技术分享 定制元类,控制类的创建
技术分享 应用:限制类内的函数必须有文档注释

 

 

元类控制类创建对象

技术分享
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print(‘__call__‘)


class Foo(metaclass=Mymeta):
    pass


obj=Foo() #Foo加括号,触发Mymeta的__call__...
技术分享

 

 

技术分享
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        #self=<class ‘__main__.Foo‘>
        #args=(‘egon‘,)
        #kwargs={‘age‘:18}
        obj=self.__new__(self) #创建对象:Foo.__new__(Foo)
        self.__init__(obj,*args,**kwargs) #初始化对象:Foo.__init__(obj,‘egon‘,age=18)
        return obj #一定不要忘记return 
class Foo(metaclass=Mymeta):
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls,*args,**kwargs)

obj=Foo(‘egon‘,age=18) #触发Mymeta.__call__

print(obj.__dict__)
技术分享
技术分享 单例模式

 

技术分享 应用:定制元类实现单例模式

 

 

技术分享 自定制元类
技术分享 自定制元类纯净版
技术分享 自定制元类精简版
技术分享
 1 #元类总结
 2 class Mymeta(type):
 3     def __init__(self,name,bases,dic):
 4         print(‘===>Mymeta.__init__‘)
 5 
 6 
 7     def __new__(cls, *args, **kwargs):
 8         print(‘===>Mymeta.__new__‘)
 9         return type.__new__(cls,*args,**kwargs)
10 
11     def __call__(self, *args, **kwargs):
12         print(‘aaa‘)
13         obj=self.__new__(self)
14         self.__init__(self,*args,**kwargs)
15         return obj
16 
17 class Foo(object,metaclass=Mymeta):
18     def __init__(self,name):
19         self.name=name
20     def __new__(cls, *args, **kwargs):
21         return object.__new__(cls)
22 
23 ‘‘‘
24 需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__
25 
26 而爹.__call__一般做两件事:
27 1.调用name.__new__方法并返回一个对象
28 2.进而调用name.__init__方法对儿子name进行初始化
29 ‘‘‘
30 
31 ‘‘‘
32 class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
33 Foo=Mymeta(‘foo‘,(...),{...})
34 因此我们可以看到,只定义class就会有如下执行效果
35 ===>Mymeta.__new__
36 ===>Mymeta.__init__
37 实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta(‘Foo‘,(...),{...})操作,
38 遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
39 于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
40 ‘‘‘
41 
42 ‘‘‘
43 obj=Foo(‘egon‘)
44 的原理同上
45 ‘‘‘
46 
47 ‘‘‘
48 总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
49 1.谁后面跟括号,就从谁的爹中找__call__方法执行
50 type->Mymeta->Foo->obj
51 Mymeta()触发type.__call__
52 Foo()触发Mymeta.__call__
53 obj()触发Foo.__call__
54 2.__call__内按先后顺序依次调用儿子的__new__和__init__方法
55 ‘‘‘
技术分享

 

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

技术分享 View Code

 

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

  1.元类帮其完成创建对象,以及初始化操作;

  2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument for key function;

  3.key作为用户自定义类产生对象的属性,且所有属性变成大写

 

ps:http://www.cnblogs.com/linhaifeng/articles/6204014.html  (参考老师博客)

  http://www.cnblogs.com/linhaifeng/articles/6182264.html#_label31  (参考老师博客)




















以上是关于Day 33(07/07)反射的主要内容,如果未能解决你的问题,请参考以下文章

反射机制入门

反射机制入门

java 反射代码片段

Day4 python反射

Day17-注解与反射

DAY21 反射(hasattr,getattr,setattr,delattr)