Python-反射

概述

运行时,区别于编译时,指的是程序被加载到内存中执行的时候
反射,reflection,指的是运行时获取类型定义信息
一个对象能够在运行时,像照镜子一样,反射处其类型信息
简单说,在python中,能够通过一个对象,找出其type、class、attribute或method的能力,称为反射或者自省
具有反射能力的函数有type()、isinstance()、callable()、dir()、getattr()等
反射相关的函数和方法
需求
有一个Point类,查看它实例的属性,并修改它。动态为实例增加属性

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return "Point({}, {})".format(self.x, self.y)

    def show(self):
        print(self.x, self.y)

p = Point(4, 5)
print(p) # Point(4, 5)
print(p.__dict__) # {'x': 4, 'y': 5}
p.__dict__['y'] = 16 # {'x': 4, 'y': 16}
print(p.__dict__) # {'x': 4, 'y': 16, 'z': 10}
p.z = 10
print(p.__dict__)
print(dir(p))
print(p.__dir__())

上例通过属性字典__dict__来访问对象的属性,本质上也是利用的反射的能力。
Python提供了内置的函数

内建函数意义
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):
        self.x = x
        self.y = y

    def __str__(self):
        return "Point({}, {})".format(self.x, self.y)

    def show(self):
        print(self)

p1 = Point(4, 5)
p2 = Point(10, 10)
print(repr(p1), repr(p2))
print(p1.__dict__) # {'x': 4, 'y': 5}
setattr(p1, 'y', 16)
setattr(p1, 'z', 16)
print(getattr(p1, '__dict__')) # {'x': 4, 'y': 16, 'z': 16}

# 动态调用方法
if hasattr(p1, 'show'):
    getattr(p1, 'show')() # Point(4, 16)

# 动态增加方法
# 为类增加方法
if not hasattr(Point, 'add'):
    setattr(Point, 'add', lambda self,other: Point(self.x +other.x, self.y + other.y))

print(Point.add) # <function <lambda> at 0x000002018E6E51E0>
print(p1.add) # <bound method <lambda> of <__main__.Point object at 0x000001EC6C2A86A0>>
print(p1.add(p2)) # 绑定 # Point(14, 26)

# 为实例增加方法,未绑定
if not hasattr(p1, 'sub'):
    setattr(p1, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))

print(p1.sub(p1, p1)) # Point(0, 0)
print(p1.sub) # <function <lambda> at 0x0000018C324E5620>

# add在谁里面, sub在谁里面
print(p1.__dict__) # {'x': 4, 'y': 16, 'z': 16, 'sub': <function <lambda> at 0x00000180472D5620>}
print(Point.__dict__)
# {'__module__': '__main__', '__init__': <function Point.__init__ at 0x00000180472D5158>,\
# '__str__': <function Point.__str__ at 0x00000180472D5268>, 'show': <function Point.show at 0x00000180472D5598>, \
# '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>,\
# '__doc__': None, 'add': <function <lambda> at 0x00000180472D51E0>}

思考

  • 这种动态增加属性的方式和装饰器修饰一个类、Mixin方式的差异在哪里

这种动态增删属性的方式是运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了,因此反射能力具有更大的灵活性

练习

  • 命令分发器,通过名称找到对应的函数执行

思路:名称找对象的方法

class Dispatcher:
    def __init__(self):
        pass

    def reg(self, name, fn):
        setattr(self, name, fn)

    def run(self):
        while True:
            cmd = input('>>>').strip()
            if cmd == 'quit':
                break

            getattr(self, cmd, lambda :print('Unknowm Cmd {}'.format(cmd)))()

dis = Dispatcher()
dis.reg('ls', lambda  :print('ls'))

dis.run()

上例中使用getattr方法找到对象的属性的方式,比自己维护一个字典来建立名词和函数之间的关系的方式好多了

反射相关的魔术方法

__getattr__()__setattrr__()__delattr__()

__getattr__()

class Base:
    n = 0

class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        return "missing {}".format(item)

p1 = Point(4,5)
print(p1.x) # 4
print(p1.z) # 6
print(p1.n) # 0
print(p1.y) # 5

实例属性会按照继承关系找,如果找不到,就会执行__getattr__()方法,如果没有这个方法,就会抛出AttrbuteError异常表示找不到属性

  • 查找属性的顺序:
    instance.__dict__ --> instance.__class__.__dict__ --> 继承的祖先类(直到object)的__dict --> 找不到调用__getattr__()

__setattrr__()

class Base:
    n = 0

class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        return "missing {}".format(item)

    def __setattr__(self, key, value):
        print("setattr {}={}".format(key,value))

p1 = Point(4,5)
print(1, p1.x)
print(2, p1.z) 
print(3, p1.n)
print(4, p1.t)
p1.x = 50
print(5, p1.x)
print(6, p1.__dict__)
p1.__dict__['x'] = 60
print(7, p1.__dict__)
print(8, p1.x)

执行结果
setattr x=4
setattr y=5
1 missing x
2 6
3 0
4 missing t
setattr x=50
5 missing x
6 {}
7 {'x': 60}
8 60

实例通过.点号设置属性,例如self.x = x属性赋值,就会调用__setattr_-(),属性要加到实例的__dict__中,就需要自己完成

class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        return "missing {}".format(item)

    def __setattr__(self, key, value):
        print("setattr {}={}".format(key,value))
        self.__dict__[key] = value # 操作字典

__setattr__()方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的__dict__

例子

__getattr____setattr__综合使用

class B:
    b = 200

class A(B):
    z = 100
    d = {}
    def __init__(self, x, y):
        self.x = x
        setattr(self, 'y', y)
        self.__dict__['a'] = 5

    def __getattr__(self, item):
        print('------', item)
        return self.d[item]

    def __setattr__(self, key, value):
        print('setattr key ', key)
        print('setattr key ', value)
        self.d[key] = value

    def __delattr__(self, item):
        print('can not del {}'.format(item))

a = A(4, 5)
print(a.__dict__)
print(A.__dict__)
print(a.x , a.y)
print(a.a)

# 执行结果
setattr key  x
setattr key  4
setattr key  y
setattr key  5
{'a': 5}
{'__module__': '__main__', 'z': 100, 'd': {'x': 4, 'y': 5},\
 '__init__': <function A.__init__ at 0x000001062E6C5158>,\
  '__getattr__': <function A.__getattr__ at 0x000001062E6C5268>, \
  '__setattr__': <function A.__setattr__ at 0x000001062E6C5598>, \
  '__delattr__': <function A.__delattr__ at 0x000001062E6C5620>, \
  '__doc__': None}
------ x
------ y
4 5
5

__delattr__()

class Point:
    Z = 5
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __delattr__(self, item):
        print('Can not del {}'.format(item))

p = Point(4, 5)
del p.x
p.z = 15
del p.z
del p.Z
print(Point.__dict__)
print(p.__dict__)
del Point.Z
print(Point.__dict__)

执行结果

Can not del x
Can not del z
Can not del Z
{'__module__': '__main__', 'Z': 5, '__init__': <function Point.__init__ at 0x0000029FBF8B5158>, \
'__delattr__': <function Point.__delattr__ at 0x0000029FBF8B5268>, '__dict__': <attribute '__dict__' of 'Point' objects>, \
'__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
{'x': 4, 'y': 5, 'z': 15}
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x0000029FBF8B5158>, \
'__delattr__': <function Point.__delattr__ at 0x0000029FBF8B5268>, '__dict__': <attribute '__dict__' of 'Point' objects>,\
 '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}

可以阻止通过实例来删除属性的操作。但是通过类依然可以删除属性

__getattribute__()

class Base:
    n = 0

class Point(Base):
    z = 6
    def __init__(self, x , y):
        self.x = x
        self.y = y

    def __getattr__(self, item):
        return "missing {}".format(item)

    def __getattribute__(self, item):
        return item

p1 = Point(4, 5)
print(p1.__dict__) # __dict__
print(p1.x) # x
print(p1.z) # z
print(p1.n) # n
print(p1.t) # t
print(Point.__dict__)
# {'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x0000016930285158>,\
 '__getattr__': <function Point.__getattr__ at 0x0000016930285268>, \
 '__getattribute__': <function Point.__getattribute__ at 0x0000016930285598>, '__doc__': None}

print(Point.z) # 6

实例的所有的属性访问,第一个都会调用__getattribute__方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常

  • 它的return值将作为属性查找的结果
  • 如果抛出AttributeError异常,则会直接调用__getattr__方法,表示属性没有找到
class Base:
    n = 0

class Point(Base):
    z = 6
    def __init__(self, x , y):
        self.x = x
        self.y = y

    def __getattr__(self, item):
        return "missing {}".format(item)

    def __getattribute__(self, item):
        # raise AttributeError("Not Found")
        # pass
        # return self.__dict__[item] # RecursionError
        return object.__getattribute__(self, item)
p1 = Point(4, 5)
print(p1.__dict__) # {'x': 4, 'y': 5}
print(p1.x) # 4
print(p1.z) # 6
print(p1.n) # 0
print(p1.t) # missing t
print(Point.__dict__)
# {'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x0000029AF7765158>, \
'__getattr__': <function Point.__getattr__ at 0x0000029AF7765268>, \
'__getattribute__': <function Point.__getattribute__ at 0x0000029AF7765598>, '__doc__': None}

print(Point.z) # 6
  • __getattribute__方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法打以访问需要的任何属性,例如object.__getattribute__(self, item)
  • 除非明确地知道__getattribute__方法用来做什么,否则不要使用它

总结

魔术方法意义
__getattr__()当通过搜索实例、实例的类及祖先类查不到属性,就会调用此方法
__setattr__()通过.点访问实例属性,进行增加、修改都要调用它
__delattr__()当通过实例来删除属性时调用此方法
__getattribute__()实例所有的属性调用都从这个方法开始
  • 属性查找顺序:
    实例调用__getattribute__() --> instance.__dict__ --> instance.__class__.__dict__ --> 继承的祖先类(直到object)的__dict__–>调用__getattr__()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值