Python魔术方法——反射

init返回值一般不写return,要写只能写return,或者是return None。

概述

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

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

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

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

具有反射能力的函数有:type(),isinstance(),callabe(),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)
print(p.__dict__)
p.__dict__["y"] = 16
print(p.__dict__)
p.z = 10
print(p.__dict__)
print(dir(p))#ordered list
print(p.__dir__())#list

结果为:
Point(4,5)
{'x': 4, 'y': 5}
{'x': 4, 'y': 16}
{'x': 4, 'y': 16, 'z': 10}
['__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__']

上例通过属性字典__dict__来访问对象的属性,本质上也是利用的反射的能力。

但是,上面的例子中,访问的方式不优雅,Python提供了内置的函数。

getattr(object,name,[default]):通过name返回object的属性值。当属性不存在,将返回default返回,如果没有default,则抛出attributeerror错误,name必须为字符串。

setattr(object,name,vaule):object的属性存在,则覆盖,不存在,新增。

hasattr(object,name):判断对象是否有这个名字的属性,name必须为字符串。

class A:
    def __init__(self):
        self.x = 5

a = A()
setattr(A,"y",10)
print(A.__dict__)
print(a.__dict__)
print(getattr(a,"x"))
print(getattr(a,"y"))
if hasattr(a,"z"):
    print(getattr(a,"z"))
    
setattr(a,"y",1000)
print(A.__dict__)
print(a.__dict__)

结果为:
{'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3D90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10}
{'x': 5}
5
10
{'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3D90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10}
{'x': 5, 'y': 1000}

用上面的方法来修改上例的代码

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),sep="\n")
print(p1.__dict__)
setattr(p1,"y",16)
setattr(p1,"z",10)
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(p1,p1))
print(p1.sub)

#add在谁里面,sub在谁里面
print(p1.__dict__)
print(Point.__dict__)

结果为:
<__main__.Point object at 0x0000000005A98940>
<__main__.Point object at 0x0000000005A98898>
{'x': 4, 'y': 5}
{'x': 4, 'y': 16, 'z': 10}
point(4,16)
<function <lambda> at 0x0000000005A9C400>
<bound method <lambda> of <__main__.Point object at 0x0000000005A98940>>
point(14,26)
point(0,0)
<function <lambda> at 0x0000000005A6E598>
{'x': 4, 'y': 16, 'z': 10, 'sub': <function <lambda> at 0x0000000005A6E598>}
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x0000000005A9CBF8>, '__str__': <function Point.__str__ at 0x0000000005A9CAE8>, 'show': <function Point.show at 0x0000000005A9CA60>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None, 'add': <function <lambda> at 0x0000000005A9C400>}

思考,这种动态增加属性的方式和装饰器修饰一个类,mixin方式的差异?

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

练习:命令分发器,通过名称找对应的函数执行。

思路:名称找对象的方法。

class dispatcher():
    
    def cmd1(self):
        print("cmd1")
        
    def run(self):
        while True:
            cmd = input("please input command: ")
            if cmd.strip() =="quit":
                return 
            getattr(self,cmd.strip(),self.defaultfn)()
            
    def defaultfn(self):
        print("default")
        
dis = dispatcher()
dis.run()

结果为:
please input command: cmd1
cmd1
please input command: a
default
please input command: cdm
default
please input command: quit

下面的是可以实现自己注册的。

class dispatcher():
    
    def cmd1(self):
        print("cmd1")
        
    def reg(self,cmd,fn):
        if isinstance(cmd,str):
            setattr(self.__class__,cmd,fn)
        else:
            print("error")
        
    def run(self):
        while True:
            cmd = input("please input command: ")
            if cmd.strip() =="quit":
                return 
            getattr(self,cmd.strip(),self.defaultfn)()
            
    def defaultfn(self):
        print("default")
        
dis = dispatcher()
dis.reg("cmd2",lambda self:print(2))
dis.reg("cmd3",lambda self:print(3))
dis.run()

结果为:
please input command: cmd1
cmd1
please input command: cmd3
3
please input command: cmd2
2
please input command: cd2
default
please input command: quit

 

class Dispatcher:
    def __init__(self):
        self._run()
        
    def cmd1(self):
        print("i'm cmd1")
    def cmd2(self):
        print("i'm cmd2")
        
    def _run(self):
        while True:
            cmd = input("plz imput a command:").strip()
            if cmd =="quit":
                break
            getattr(self,cmd,lambda:print("unknown command {}".format(cmd)))()
            
Dispatcher()

结果为:
plz imput a command:1
unknown command 1
plz imput a command:3
unknown command 3
plz imput a command:quit
<__main__.Dispatcher at 0x5825940>

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

class A:
    def __init__(self):
        self.x = 5

a = A()
setattr(A,"y",10)
print(A.__dict__)
print(a.__dict__)
print(getattr(a,"x"))
print(getattr(a,"y"))
if hasattr(a,"z"):
    print(getattr(a,"z"))
    
setattr(a,"y",1000)
print(A.__dict__)
print(a.__dict__)

setattr(a,"mtd",lambda self:1)
print(A.__dict__)
print(a.__dict__)

结果为:
{'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3C80>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10}
{'x': 5}
5
10
{'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3C80>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10}
{'x': 5, 'y': 1000}
{'__module__': '__main__', '__init__': <function A.__init__ at 0x00000000059A3C80>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10}
{'x': 5, 'y': 1000, 'mtd': <function <lambda> at 0x00000000059E4400>}

上例中的方法添加到了实例的属性中。但是调用的时候会出错,要不出错。

class A:
    def __init__(self):
        self.x = 5

a = A()
setattr(A,"y",10)
print(A.__dict__)
print(a.__dict__)
print(getattr(a,"x"))
print(getattr(a,"y"))
if hasattr(a,"z"):
    print(getattr(a,"z"))
    
setattr(a,"y",1000)
print(A.__dict__)
print(a.__dict__)

setattr(A,"mtd",lambda self:1)
print(A.__dict__)
print(a.__dict__)
print(a.mtd())

结果为:
{'__module__': '__main__', '__init__': <function A.__init__ at 0x0000000005C3DD90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10}
{'x': 5}
5
10
{'__module__': '__main__', '__init__': <function A.__init__ at 0x0000000005C3DD90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10}
{'x': 5, 'y': 1000}
{'__module__': '__main__', '__init__': <function A.__init__ at 0x0000000005C3DD90>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'y': 10, 'mtd': <function <lambda> at 0x0000000005C3D6A8>}
{'x': 5, 'y': 1000}
1

 

反射相关的魔术方法

__getattr__(),__setattr__(),__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)
print(p1.z)
print(p1.n)
print(p1.t)

结果为:
4
6
0
missing t

一个类的属性会按照继承关系找,如果找不到,就会执行__getattr__方法,如果没有这个方法。就会抛出attributeerror异常,表示找不到属性。

查找属性顺序为:

 

 __setattr__()方法

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(p1.x)#missing ,why
print(p1.z)
print(p1.n)
print(p1.t)#missing

p1.x = 50
print(p1.__dict__)
p1.__dict__["x"] = 60
print(p1.__dict__)
print(p1.x)

结果为:
setattr x = 4
setattr y = 5
missing x
6
0
missing t
setattr x = 50
{}
{'x': 60}
60
 

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

class Base:
    n = 5
    
class A(Base):
    m = 6
    def __init__(self,x):
        self.x = x
        
    def __setattr__(self,key,value):
        print("__setattr__",key,value)
        
a  = A(10)
a.x = 100
        

结果为:
__setattr__ x 10
__setattr__ x 100
class Base:
    n = 5
    
class A(Base):
    m = 6
    def __init__(self,x):
        self.x = x
        
    def __setattr__(self,key,value):
        print("__setattr__",key,value)
        
    def __getattr__(self,item):
        print("__getattr__",item)
        
a  = A(10)
a.x = 100
a.y = 200
print(a.__dict__)
a.y

结果为:
__setattr__ x 10
__setattr__ x 100
__setattr__ y 200
{}
__getattr__ y

为什么后面的a.y没有访问到。

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
    
p1 = Point(4,5)
print(p1.x)#missing ,why
print(p1.z)
print(p1.n)
print(p1.t)#missing

p1.x = 50
print(p1.__dict__)
p1.__dict__["x"] = 60
print(p1.__dict__)
print(p1.x)

结果为:
setattr x = 4
setattr y = 5
4
6
0
missing t
setattr x = 50
{'x': 50, 'y': 5}
{'x': 60, 'y': 5}
60

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

__delattr__()

class Point(Base):
    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 0x0000000005F3BD90>, '__delattr__': <function Point.__delattr__ at 0x0000000005F3BE18>, '__doc__': None}
{'x': 4, 'y': 5, 'z': 15}
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x0000000005F3BD90>, '__delattr__': <function Point.__delattr__ at 0x0000000005F3BE18>, '__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__)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)
print(Point.__dict__)
print(Point.z)

结果为:
__dict__
x
z
n
t
{'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x0000000005C3D620>, '__getattr__': <function Point.__getattr__ at 0x0000000005C3DEA0>, '__getattribute__': <function Point.__getattribute__ at 0x0000000005C3D510>, '__doc__': None}
6

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

它的return值将作为属性查找的结果,如果抛出attributeerrror异常,则会直接调用__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):
        return object.__getattribute__(self,item)
    
p1 = Point(4,5)
print(p1.__dict__)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)
print(Point.__dict__)
print(Point.z)

结果为:
{'x': 4, 'y': 5}
4
6
0
missing t
{'__module__': '__main__', 'z': 6, '__init__': <function Point.__init__ at 0x0000000005C3D400>, '__getattr__': <function Point.__getattr__ at 0x0000000005C3DE18>, '__getattribute__': <function Point.__getattribute__ at 0x0000000005C3DC80>, '__doc__': None}
6

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

总结:

 
 

posted on 2019-11-21 21:00  xpc199151  阅读(145)  评论(0编辑  收藏  举报

导航