Python-面向对象魔术方法之反射

反射

概述

运行时,runtime,区别在于编译时,指的是程序被加载到内存中执行的时候。

反射,reflection,指的是运行时获取类型定义信息。

一个对象能够在运行时像镜子一样反射出其类型信息。

简单说,在Python中,能够通过一个对象,找到其type、class、attribute或method的能力,称之为反射或自省

具有反射能力的函数有type()、isinstance()、called()、dir()、getatter()等

 

反射相关的函数和方法

需求,有一个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)
print(p.__dict__)
p.__dict__['y'] = 10
print(p.__dict__)

p.z = 20
print(p.__dict__)
print(dir(p))
print(p.__dir__())
print(Point.__dict__)

#输出
<Point 4,5>
{'x': 4, 'y': 5}
{'x': 4, 'y': 10}
{'x': 4, 'y': 10, 'z': 20}
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'show', 'x', 'y', 'z']
['x', 'y', 'z', '__module__', '__init__', '__str__', 'show', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x0000021625775AE8>, '__str__': <function Point.__str__ at 0x0000021625775B70>, 'show': <function Point.show at 0x000002162579AD08>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}

上例通过属性字典__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,20)

print(repr(p1),repr(p2))
print(p1.__dict__)
setattr(p1,'y',16)
setattr(p1,'z',100)
print(p1.__dict__)
print(getattr(p1,'__dict__'))

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


#动态增加方法
#为类增加方法

if not hasattr(Point, 'add'):
    setattr(Point, 'add', lambda self,other: Point(self.x + other.x, self.y + other.y))

print(Point.add)
print(p1.add)
print(p1.add(p2))

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(Point.__dict__)

#输出
<__main__.Point object at 0x00000172971D82E8> <__main__.Point object at 0x00000172971BD2E8>
{'x': 4, 'y': 5}
{'x': 4, 'y': 16, 'z': 100}
{'x': 4, 'y': 16, 'z': 100}
<Point 4,16>
<function <lambda> at 0x00000172971A5950>
<bound method <lambda> of <__main__.Point object at 0x00000172971D82E8>>
<Point 14,36>
<function <lambda> at 0x00000172971CB6A8>
<Point -6,-4>
{'x': 4, 'y': 16, 'z': 100, 'sub': <function <lambda> at 0x00000172971CB6A8>}
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x00000172971A5AE8>, '__str__': <function Point.__str__ at 0x00000172971A5B70>, 'show': <function Point.show at 0x00000172971CBD08>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None, 'add': <function <lambda> at 0x00000172971A5950>}

思考

这种动态增加属性的方式和装饰器装饰一个类、Mixin方式的差异?

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

 

反射相关的魔术方法

__getattr__()、__setattr__()、__delattr__()这三个魔术方法,分别测试这三个方法

 

__getattr__()

class Base:
    n = 0

class Point(Base):
    z = 10
    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)
print(p1.z)
print(p1.n)
print(p1.m)

#输出
4
10
0
Missing m

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

 

查找顺序为:

instance.__dict__ --> instance.__class__.__dict__-->继承的祖先类(直到object基类)的__dict__-->找不到-->调用__getattr__()

 

__setattr__()

class Base:
    n = 0

class Point(Base):
    z = 10
    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(p1.x)
print(p1.z)
print(p1.n)
print(p1.m)
p1.x = 100
print(p1.x)
print(p1.__dict__)
p1.__dict__['x'] = 20
print(p1.__dict__)
print(p1.x)

#输出结果
setattr x=4
setattr y=5
Missing x
10
0
Missing m
setattr x=100
Missing x
{}
{'x': 20}
20

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

class Base:
    n = 0

class Point(Base):
    z = 10
    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


p1 = Point(4,5)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.m)
p1.x = 100
print(p1.x)
print(p1.__dict__)
p1.__dict__['x'] = 20
print(p1.__dict__)
print(p1.x)

#输出
setattr x=4
setattr y=5
4
10
0
Missing m
setattr x=100
100
{'x': 100, 'y': 5}
{'x': 20, 'y': 5}
20

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

class Base:
    n = 100

class Point(Base):
    z = 60
    d = {}
    def __init__(self, x, y):
        self.x = x
        # self.y = y
        setattr(self,'y',y)
        self.__dict__['a'] = 5

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

    def __getattr__(self, item):
        print("missing {}".format(item))
        return self.d[item]

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

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

p1 = Point(4,5)
print(p1.x,p1.y)
print(p1.__dict__)
print(p1.a)
print(p1.d)
print(Point.__dict__)


#输出
setattr x=4
setattr y=5
missing x
missing y
4 5
{'a': 5}
5
{'x': 4, 'y': 5}
{'__module__': '__main__', 'z': 60, 'd': {'x': 4, 'y': 5}, '__init__': <function Point.__init__ at 0x000002118E635AE8>, 'show': <function Point.show at 0x000002118E635B70>, '__getattr__': <function Point.__getattr__ at 0x000002118E65BD08>, '__setattr__': <function Point.__setattr__ at 0x000002118E65B6A8>, '__delattr__': <function Point.__delattr__ at 0x000002118E65B8C8>, '__doc__': None}

 

__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)
print(p.x)
del p.x
p.Z = 10
del p.z
del p.Z
print(p.__dict__)
print(p.__class__.__dict__)
del Point.z
print(Point.__dict__)

#输出
4
Can not del x
Can not del z
Can not del Z
{'x': 4, 'y': 5, 'Z': 10}
{'__module__': '__main__', 'z': 5, '__init__': <function Point.__init__ at 0x000001C8DCFD5AE8>, '__delattr__': <function Point.__delattr__ at 0x000001C8DCFD5B70>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x000001C8DCFD5AE8>, '__delattr__': <function Point.__delattr__ at 0x000001C8DCFD5B70>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}

通过实例删除属性,就会尝试调用该魔术方法,可以阻止通过实例来删除属性的操作,但是通过类依然能删除类的属性。

 

__getattribute__()

class Base:
    n = 10

class Point(Base):
    z = 1
    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__)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.m)
print(Point.__dict__)
print(Point.z)

#输出
__dict__
x
z
n
m
{'__module__': '__main__', 'z': 1, '__init__': <function Point.__init__ at 0x000002BD3F595AE8>, '__getattr__': <function Point.__getattr__ at 0x000002BD3F595B70>, '__getattribute__': <function Point.__getattribute__ at 0x000002BD3F5BAD08>, '__doc__': None}
1

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

  1、它的return值将作为属性查找的结果

  2、如果抛出AttributeError异常,则会直接调用__getattr__()方法,因为表示属性没有找到。

class Base:
    n = 10

class Point(Base):
    z = 1
    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]
        return super().__getattribute__(item)
    

p1 = Point(4,5)
print(p1.__dict__)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.m)
print(Point.__dict__)
print(Point.z)

#输出
{'x': 4, 'y': 5}
4
1
10
Missing m
{'__module__': '__main__', 'z': 1, '__init__': <function Point.__init__ at 0x000002D6BFAD5AE8>, '__getattr__': <function Point.__getattr__ at 0x000002D6BFAD5B70>, '__getattribute__': <function Point.__getattribute__ at 0x000002D6BFAFAD08>, '__doc__': None}
1

__getattribute__方法中为了 避免在该方法中无限的递归,它的实现应该调用基类的同名方法以访问任何属性,例如object.__getattribute__(self, item)

注意:除非你知道__getattribute__方法用来做什么,否则不要用。

 

总结

魔术方法 意义
__getattr__() 当通过搜索实例、实例的类或祖先类查找不到属性,就会调用次方法,,如没次方法,则抛出AttributeError错误
__setattr__() 通过.访问实例属性,进行增加、修改都要调用它
__delattr__() 当通过实例来删除属性时调用此方法
__getattribute__() 实例所有的属性调用都从这个方法开始

 

posted @ 2020-06-25 20:06  Alrenn  阅读(116)  评论(0)    收藏  举报