29期第十五周笔记

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先初始化,因此顺序为

    1. 打印A.init
    2. 打印B.init
    3. 实例化并初始化B的实例t
    4. 打印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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值