Week 15
本周学习主要内容包括反射(及其魔术方法)、描述器*
反射
- 运行时(runtime),区别于编译时,指的是程序被加载到内存中执行的时候
- 反射(reflection)值运行时获取类型定义信息
- 一个对象能在运行时像照镜子一样反射出其(自己的)类型信息
- 反射或自省在Python中指:能够通过一个对象,找到其type、class、attribute或method的能力
- 具有反射能力的函数:type(), isinstance(), callable(), dir(), getattr()等
相关函数及方法
- 相关的三个内建函数:
内建函数 | 意义 |
---|---|
getattr(object, name[, default]) | 通过name返回object的属性值,当属性不存在,将使用default返回,如果没有default,抛出AttributeError,name必须是字符串 |
setattr(object, name, value) | object的属性存在,则覆盖,不存在,新增 |
hasattr(object, name) | 判断对象是否有这个名字的属性,name必须为字符串 |
class Point:
def __init__(self,x,y): #add sub
self.x = x
self.y = y
def show(self):
return "<Point {}, {}>".format(self.x,self.y)
__str__ = show
p1 = Point(4,5)
p2 = Point(10,10)
if not hasattr(Point,'add'): #类属性
# Point.add = lambda self,other:Point(self.x + other.x, self.y + other.y) 上下等价
setattr(Point,'add',lambda self,other:Point(self.x + other.x, self.y + other.y))
print('='*30)
# print(p1.add,'****')
# print(p1.add(p2).show()) #self = p1,p2
print(getattr(p1,'add')(p2)) #和上一行等价
print('-'*30)
# def test(self,other='teststr'):
# print(1,self)
# print(2,other)
if not hasattr(p1,'sub'): #在实例上可以写方法吗?可以,但由于他是实例属性,通过实例属性调用该方法,没有绑定效果
setattr(p1,'sub',
lambda self,other:Point(self.x - other.x, self.y - other.y)
)
print(p1.sub,'****')
print(p1.sub(p1,p2))
print(p1.__dict__)
print(p2.__dict__)
print(p1.__class__.__dict__)
print(p2.__class__.__dict__)
print(hasattr(p1,'abc'))
#p1.abc = 'abc'
setattr(p1,'abc','abc') #前一个abc是标识符后一个是值,同上一行等价
print(p1.__dict__)
比较装饰器和Mixin:
- 这种动态增删属性的方式是运行时改变类或实力的方式
- 但装饰器和Mixin都是定义时就决定了
- 因此反射能力具有更大灵活性
相关魔术方法:
魔术方法 | 意义 |
---|---|
__getattr__() | 当通过搜索实例、实例的类及祖先类查不到属性,就会调用该方法 |
__setattr__() | 通过 . 访问实例属性,进行增加、修改都要调用它 |
__delattr__() | 当通过实例来删除属性时调用此方法 |
__getattribute__ | 实例所有的属性调用都要从这个方法开始 |
__getattr__()
class Base:
n = 77
class Point(Base):
z = 66
def __init__(self,x,y):
self.x = x
self.y = y
def __getattr__(self, item): #self,与实例有关
#有了这个魔术方法,不是通过实例访问属性就调用的
#而是通过实例访问引发AttributeError,如果有此魔术方法则调用
print('getattr ~~~~ {}'.format(item)) #item是什么类型 - str
print(type(item)) #属性名称字符串,反射
return 10000
p1 = Point(4,5)
#print(Point.z, Point.n) #类属性搜索顺序,与getattr无关
#调用第12行吗?
print(p1.x)
print(p1.y)
print(p1.z)
print(p1.n)
print(p1.item) #如果属性的搜索顺序中找不到,报AttributeError
print(p1.xyz)
print('-'*30)
#print(Point.xyz)
- 实例属性会按照继承关系找,如果找不到会执行该方法,如果没有这个方法,会抛出AttributeError异常表示找不到属性
- 查找属性顺序:
instance.__dict__ --> instance.__class__.__dict__ --> 继承的祖先类(直到object)的__dict__ --> 找不到 --> 调用__getattr__()
__setattr__()
class Base:
n = 77
class Point(Base):
z = 66
def __init__(self,x,y):
self.x = x
self.y = y
def __setattr__(self, key, value): pass
p1 = Point(4,5)
# print(1,p1.x) #AttributeError
# print(2,p1.y) #AttributeError
print(3,p1.z)
print(4,p1.n)
#print(5,p1.xyz) #AttributeError
print('-'*30)
- 该方法可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的
__dict__
- 将
__getattr__
和__setattr__
一起使用:
class Base:
n = 77
class Point(Base):
z = 66
def __init__(self,x,y):
print('init start~~~~~~~')
self.x = x #对实例赋值
self.y = y
# setattr(self,'y',y)
print('init end~~~~~~~~')
def __getattr__(self, prop):
print('missing prop = {}'.format(prop))
def __setattr__(self, key, value): #只要是实例的属性赋值就调用他
print('setattr~~~~~~~~~')
# super().__setattr__(key,value)
self.__dict__[key] = value
# setattr(self,key,value) #递归 self.key = value
p1 = Point(4,5)
p1.a = 'aaaa'
print(p1.__dict__) #实例p1没有属性了
print(p1.x,p1.y,p1.a)
# print(1,p1.x)
# print(2,p1.y)
print(3,p1.z)
print(4,p1.n)
print(5,p1.xyz)
## 另一种使用方式:(换字典)
class Base:
n = 77
class Point(Base):
z = 66
d = {}
def __init__(self,x,y):
print('init start~~~~~~~')
self.x = x #对实例赋值
#self.y = y
setattr(self,'y',y)
#setattr(self.__dict__,'a','aaaa') #不是设置kv对,是强行添加属性,写法不对
self.__dict__['a'] = 'aaaa'
print('init end~~~~~~~~')
def __getattr__(self, key):
print('missing prop = {}'.format(key))
return self.d[key]
def __setattr__(self, key, value): #只要是实例的属性赋值就调用他
print('setattr~~~~~~~~~',key)
self.d[key] = value
p1 = Point(4,5)
print(1,p1.x)
print(2,p1.y)
print(3,p1.a)
print(4,p1.n)
print(p1.__dict__)
print(Point.__dict__)
__delattr__()
- 跟实例相关删除属性时才调用(不重要 了解 少用)
- 通过实例删除属性,就会尝试调用该魔术方法,可以阻止通过实例来删除属性的操作
- 但通过类依然可以删除类属性
class Base:
n = 77
class Point(Base):
z = 66
d = {}
def __init__(self,x,y):
print('init start~~~~~~~')
self.x = x #对实例赋值
#self.y = y
setattr(self,'y',y)
#setattr(self.__dict__,'a','aaaa') #不是设置kv对,是强行添加属性,写法不对
self.__dict__['a'] = 'aaaa'
print('init end~~~~~~~~')
def __getattr__(self, key):
print('missing prop = {}'.format(key))
return self.d[key]
def __setattr__(self, key, value): #只要是实例的属性赋值就调用他
print('setattr~~~~~~~~~',key)
self.d[key] = value
def __delattr__(self, item):
print('cannot del {}'.format(item))
p1 = Point(4,5)
print(Point.__dict__.keys())
# del Point.z
del p1.z #调用了__delattr__但没有删除成功
print(Point.__dict__.keys())
__getattribute__
class Base:
n = 77
class Point(Base):
z = 66
def __init__(self,x,y):
print('init start~~~~~~~')
self.x = x #对实例赋值
self.y = y
print('init end~~~~~~~~')
def __getattr__(self, key):
print('missing prop = {}'.format(key))
# def __setattr__(self, key, value): # 只要是实例的属性赋值就调用他
# print('setattr++++++++')
# super().__setattr__(key,value)
def __getattribute__(self, item):
#不管找不找得到
#这个魔术方法一般不用
#如果写了,一定是通过实例访问属性的第一站
#如果不知道该方法用途不要用
print('*** {} ***'.format(item))
# return None
# return 10001
# raise Exception()
# return self.__dict__[item] 递归,不能这样写
# raise AttributeError('找不到属性了') #有__getattr__方法就调用,没有就抛异常
# return object.__getattribute__(self,item) #转而调用基类,默认方式属性搜索方式,相当于绕过__getattribute__
return super().__getattribute__(item) #等价上,属性搜索顺序的默认方式,高级控制
p1 = Point(4,5)
print(1,p1.x)
# print(2,p1.z)
# print(3,p1.n)
# print(Point.z,Point.n)
# print(4,p1.__dict__) #- None(等效于L27): 字典还在,但通过p1访问被getattribute截胡了
-
实例的所有属性访问,第一个都会调用该方法,他阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常
- 他的return值将作为属性查找的结果
- 如果抛出AttributeErro异常,则会直接调用
__getattr__
方法,因为表示属性没有找到
-
该方法为了避免无限递归,他的视线应该调用基类的同名方法以访问需要的任何属性,如
object.__getattribute__(self, name)
-
注意:除非明确知道该方法作用否则不要使用!!
描述器 Descriptors
表现
相关三个魔术方法:__get__()
、__set__()
、__delete__()
方法签名:
object.__get__(self,instance,owner)
object.__set__(self,instance,value)
object.__delete__(self,instance)
- self指代当前实例,调用者
- instance是owner的实例
- owner是属性的所属类
class A:
def __init__(self):
print('A init ~~~~~~~~~')
self.a1 = 'a1'
def __get__(self, instance, owner):
print('A.get',self,instance,owner)
#owner属主类,B
return self
def __repr__(self):
return '<A instance {}>'.format(id(self))
class B: #属主类
x = A() #A() 描述器实例
# 访问B或者B实例属性变成一个函数调用
def __init__(self):
print('B init ~~~~~~~~~')
# self.y = A() #描述器不能用在实例属性上,要放在属主类的类属性上
def __repr__(self):
return '<B instance {}>'.format(id(self))
print('-'*30)
print(B.x) #A的实例 #B.x => A().__get__()
print(B.x.a1) #'a1'
print('='*30)
t = B()
print(id(t))
print(t.x) # t.x -> B.x -> A().__get__()
print(t.x.a1)
-
上例中执行顺序:类加载时,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,因此顺序为
- 打印A.init
- 打印B.init
- 实例化并初始化B的实例t
- 打印t.x.a1,t.x会查找类属性,指向A的实例,因此返回类A实例的属性a1的值
-
因为定义了
__get__
方法,类A就是一个描述器,使用类B或者类B的实例来对x属性读取,就是对类A的实例的访问,就会调用__get__
方法 -
self,instance,owner三个参数的含义:
- self对应的都是A的实例
- owner对应的都是B类
- instance说明:
- None表示不是B类的实例,对应调用B.x
<__main__.B object at 0x000001F4D3BDA308>
表示是B的实例,对应调用B().x
描述器定义
Python中,一个类实现了__get__
、__set___
、__delete__
三个方法中任何一个,就是描述器
实现这三个中的某些方法,就支持了描述器协议
- 仅实现
__get__
,就是非数据描述器 non-data descriptor
class A:
def __init__(self):
print('A init ~~~~~~~~~')
self.a1 = 'a1'
def __get__(self, instance, owner):
print('A.get',self,instance,owner)
#owner属主类,B
return self
def __repr__(self):
return '<A instance {}>'.format(id(self))
class B: #属主类
x = A() #A() 描述器实例
# 访问B或者B实例属性变成一个函数调用
def __init__(self):
print('B init ~~~~~~~~~')
self.x = 'b.x'
# self.y = A() #描述器不能用在实例属性上,要放在属主类的类属性上
def __repr__(self):
return '<B instance {}>'.format(id(self))
- 实现了
__get__
、__set___
,就是数据描述器 data descriptor
class A:
def __init__(self):
print('A init ~~~~~~~~~')
self.a1 = 'a1'
def __get__(self, instance, owner):
print('A.get',self,instance,owner)
#owner属主类,B
return self
def __set__(self, instance, value):
pass
def __repr__(self):
return '<A instance {}>'.format(id(self))
class B: #属主类
x = A() #A() 描述器实例
# 访问B或者B实例属性变成一个函数调用
def __init__(self):
print('B init ~~~~~~~~~')
self.x = 'b.x'
# self.y = A() #描述器不能用在实例属性上,要放在属主类的类属性上
def __repr__(self):
return '<B instance {}>'.format(id(self))
如果一个类的类属性设置为描述器实例,被称为owner属主
当该类的类属性被查找、设置、删除时,就会调用描述器相应的方法
属性的访问顺序
- 实例的
__dict__
优先于非数据描述器 - 数据描述器优先于实例的
__dict___
__delete__
方法有同样效果,有这个方法也是数据描述器
class A:
def __init__(self):
print('A init ~~~~~~~~~')
self.a1 = 'a1'
def __get__(self, instance, owner):
print('A.get',self,instance,owner)
# owner属主类,B
return self
def __set__(self, instance, value):
print('A.set')
print(self.__dict__)
# self.data = value
self.__dict__[self.prop_name] = value
print(self.__dict__)
class B: # 属主类
x = A() # A() 描述器实例
# 访问B或者B实例属性变成一个函数调用
def __init__(self):
print('B init start ~~~~~~~~~')
self.x = 'b.x'
# 数据描述器,如果这个属性x是类属性,而类属性是描述器,那么实例属性赋值
# 就成了调用A().__set__()
# 数据描述器优先级 > __dict__ > 非数据描述器
# self.y = A() # 描述器不能用在实例属性上,要放在属主类的类属性上
# print('<B id={}>'.format(id(self)))
print('B init end ~~~~~~~~~')
新增方法 __set_name__
- 在属主类构建时就会调用
- 提供这个方法就可以知道属主类和属主类的类属性名
class A:
def __init__(self):
print('A init ~~~~~~~~~')
self.a1 = 'a1'
def __get__(self, instance, owner):
print('A.get',self,instance,owner)
# owner属主类,B
return self
def __set__(self, instance, value):
print('A.set')
print(self.__dict__)
# self.data = value
self.__dict__[self.prop_name] = value
print(self.__dict__)
def __set_name__(self, owner, name):
print(self,owner,name,'***')
self.prop_name = name
def __repr__(self):
return '<A instance {}>'.format(id(self))
class B: # 属主类
x = A() # A() 描述器实例
# 访问B或者B实例属性变成一个函数调用
print('-'*30)
t = B()
print(t.x) # t.x -> B.x -> A().__get__()
## 运行结果:
------------------------------
A.get <__main__.A object at 0x000001F4D3BE8FC8> <__main__.B object at 0x000001F4D3BE9408> <class '__main__.B'>
<__main__.A object at 0x000001F4D3BE8FC8>
练习及参考:
1. 实现StaticMethod装饰器
class StaticMethod:
def __init__(self,fn):
self.__fn = fn
# 非数据描述器,提供__get__
def __get__(self, instance, owner):
print('get~~~~~~~')
return self.__fn
class A: #属主类
def __init__(self):
pass
# foo = StaticMethod(foo) #一个类属性等于描述器的实例 => 符合描述器要求
@StaticMethod
def foo(): #foo = StaticMethod(foo) => foo就是StaticMethod的实例
print('static method ~~~~~~~')
print(A.foo)
print(A.__dict__)
print(A.foo())
print(A().foo())
## 运行结果:
get~~~~~~~
<function A.foo at 0x000001DB3446C318>
{'__module__': '__main__', '__init__': <function A.__init__ at 0x000001DB3434F8B8>, 'foo': <__main__.StaticMethod object at 0x000001DB34466088>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
get~~~~~~~
static method ~~~~~~~
None
get~~~~~~~
static method ~~~~~~~
None
2. 实现ClassMethod装饰器
from functools import partial
class ClassMethod:
def __init__(self, fn):
self.__fn = fn
# 非数据描述器,提供__get__
def __get__(self, instance, owner):
print('get=======')
# return (self.__fn(owner) # 使用属主类A 固定owner返回新函数
return partial(self.__fn, owner) #
class A: #属主类
def __init__(self):
pass
@ClassMethod
def bar(cls): #bar = ClassMethod(bar) -> bar是ClassMethod的实例
print('class method ~~~~~~~~', cls)
return 123
print(A.__dict__)
print(A.bar)
print(A.bar()) #cls
print(A().bar())
## 运行结果:
{'__module__': '__main__', '__init__': <function A.__init__ at 0x000001DB34447828>, 'bar': <__main__.ClassMethod object at 0x000001DB3443AC48>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
get=======
functools.partial(<function A.bar at 0x000001DB34447E58>, <class '__main__.A'>)
get=======
class method ~~~~~~~~ <class '__main__.A'>
123
get=======
class method ~~~~~~~~ <class '__main__.A'>
123