python 的魔法方法
这里只分析几个可能会常用到的魔法方法,像__new__这种不常用的,用来做元类初始化的或者是__init__这种初始化使用的 每个人都会用的就不介绍了。
其实每个魔法方法都是在对内建方法的重写,和做像装饰器一样的行为。理解这个道理 再尝试去理解每个细节装饰器会比较方便。
关于__str__和__repr__:
直接上例子:
class Test(object):
def __init__(self, world):
self.world = world
def __str__(self):
return "world is %s str" % self.world
def __repr__(self):
return "world is %s repr" % self.world
t = Test("world_big")
print str(t)
print repr(t)output:
world is world_big strworld is world_big repr
其实__str__相当于是str()方法 而__repr__相当于repr()方法。str是针对于让人更好理解的字符串格式化,而repr是让机器更好理解的字符串格式化。
其实获得返回值的方法也很好测试,在我们平时使用ipython的时候,在不使用print直接输出对象的时候,通常调用的就是repr方法,这个时候改写repr方法可以让他方便的输出我们想要知道的内容,而不是一个默认内容。
关于__hash__和__dir__:
其实在实际应用中写了这么久python,也没有用到需要这两个方法出现的地方,但是在有些库里面是有看到过。
__hash__是hash()方法的装饰器版本,而__dir__是dir()的装饰器版本。
上代码展示一下__hash__用法:
class Test(object):
def __init__(self, world):
self.world = world
x = Test("world")
p = Test("world")
print hash(x) == hash(p)
print hash(x.world) == hash(p.world)
class Test2(object):
def __init__(self, song):
self.song = song
def __hash__(self):
return 1241
x = Test2("popo")
p = Test2("janan")
print x, hash(x)
print p, hash(p)output:
FalseTrue<__main__.Test2 object at 0x101b0c590> 1241<__main__.Test2 object at 0x101b0c4d0> 1241
复制代码
可以看到这里的hash()方法总是会返回int型的数字。可以用于比较一个唯一的对象,比方说一个不同内存的object不会相当,而相同字符串hash之后就会相等。然后我们通过修改__ hash__方法来修改hash函数的行为。让他总是返回1241,也是可以轻松做到的。
另外一个方法是dir(),熟悉python的人都知道dir()可以让我们查看当前环境下有哪些方法和属性可以进行调用。如果我们使用dir(object)语法,可以获得一个对象拥有的方法和属性。
同样的道理如果我们在类中定义了__dir__(),就可以指定哪些方法和属性能够被dir()方法所查看查找到。道理一样我这里不再贴出代码了,有兴趣的朋友可以自己去试试。
关于控制参数访问的__getattr__, setattr, delattr, getattribute:
__getattr__是一旦我们尝试访问一个并不存在的属性的时候就会调用,而如果这个属性存在则不会调用该方法。
来看一个__getattr__的例子:
class Test(object):
def __init__(self, world):
self.world = world
def __getattr__(self, item):
return item
x = Test("world123")
print x.world4output:world4
这里我们并没有world4属性,在找不到属性的情况下,正常的继承object的对象都会抛出AtrributeError的错误。但是这里我通过__getattr__魔法方法改变了找不到属性时候的类的行为。输出了查找的属性的参数。
__setattr__是设置参数的时候会调用到的魔法方法,相当于设置参数前的一个钩子。每个设置属性的方法都绕不开这个魔法方法,只有拥有这个魔法方法的对象才可以设置属性。在使用这个方法的时候要特别注意到不要被循环调用了。
下面来看一个例子:
"""
遇到问题没人解答?小编创建了一个Python学习交流群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
"""
class Test(object):
def __init__(self, world):
self.world = world
def __setattr__(self, name, value):
if name == "value":
object.__setattr__(self, name, value - 100)
else:
object.__setattr__(self, name, value)
x = Test(123)
print x.world
x.value = 200
print x.valueoutput:123100
这里我们先初始化一个Test类的实例x,通过__init__方法我们可以注意到,会给初始化的world参数进行赋值。这里的self.world = world语句就是在做这个事情。
注意,这里在进行world参数赋值的时候,就是会调用到__setattr__方法。这个例子来看world就是name,而后面的值的world就是value。我在__setattr__里面做了一个行为改写,我将判断name 值是"value"的进行特殊处理,把它的value值减少100. 所以输出了预期的结果。
我为什么说__setattr__特别容易出现循环调用?因为任何赋值方法都会走这个魔法方法,如果你在你改写__setattr__方法里面使用了类似的赋值,又回循环调用回__setattr__方法。例如:
class Test(object):
def __init__(self, world):
self.world = world
def __setattr__(self, name, value):
self.name = value
x = Test(123)
print x.worldoutput:RuntimeError: maximum recursion depth exceeded
这里我们想让__setattr__执行默认行为,也就是将value赋值给name,和object对象中的同样方法,做类似的操作。但是这里我们不调用父类__setattr__的方法来实现,做这样的尝试得到的结果就是,超过循环调用深度,报错。因为这里在执行初始化方法self.world = world的时候,就会调用__setattr__方法,而这里的__setattr__方法里面的self.name = value又会调用自身。所以造成了循环调用。所以使用该魔法方法的时候要特别注意。
__delattr__的行为和__setattr__特别相似,同样需要注意的也是循环调用问题,其他都差不多,只是把属性赋值变成了 del self.name这样的表示。下面直接上个例子,不再多赘述。
class Test(object):
def __init__(self, world):
self.world = world
def __delattr__(self, item):
print "hahaha del something"
object.__delattr__(self, item)
x = Test(123)
del x.world
print x.worldoutput:
hahaha del somethingTraceback (most recent call last):File "/Users/piperck/Desktop/py_pra/laplace_pra/practie_01_23/c2.py", line 12, in <module>print x.worldAttributeError: "Test" object has no attribute "world"
可以看到我们将属性删除之后,就找不到那个属性了。但是在删除属性的时候调用了__delattr__,我在里面打印了一段话,在执行之前被打印出来了
__getattribute__和__getattr__方法唯一不同的地方是,上面我们已经介绍了__getattr__方法只能在找不到属性的时候拦截调用,然后进行重载或者加入一些其他操作。但是__getattribute__更加强大,他可以拦截所有的属性获取。所以也容易出现我们上面提到的,循环调用的问题。下面上一个例子来说明这个问题:
class Test(object):
def __init__(self, world):
self.world = world
def __getattribute__(self, item):
print "get_something: %s" % item
return item
x = Test(123)
print x.world
print x.ppoutput:get_something: worldworldget_something: pppp
可以看到,区别于__getattr__只拦截不存在的属性,__getattribute__会拦截所有的属性。所以导致了已经被初始化的world值123,也被改写成了字符串world。而不存在的属性也被改写了成了pp。
关于__dict__:
先上个例子:
class Test(object):
fly = True
def __init__(self, age):
self.age = age
__dict__魔法方法可以被称为系统,他是存储各分层属性的魔法方法。__dict__中,键为属性名,值为属性本身。可以这样理解,在平时我们给类和实例定义的那些属性,都会被存储到__dict__方法中用于读取。而我们平时使用的类似这样的语法Test.fly 其实就是调用了类属性,同样可以写成Test.dict["fly"]。除了类属性,还有实例属性。当我们用类实例化一个实例,例如上文我们使用p = Test(2)实例化类Test,p也会具有__dict__属性。这里会输出:
{"age": 2}
由上可以发现,python中的属性是进行分层定义的。/object/Test/p这样一层一层下来的。当我们需要调用某个属性的时候,python会一层一层往上面遍历上去。先从实例,然后实例的__class__的__dict__,然后是该类的__base__。这样__dict__一路找上去。如果最后都没有找到,就抛出AttributeError错误。
这里可以延伸一下,没记错的话,我前面有篇文章讲了一个方法__slot__。__ slots__ 方法就是通过限制__dict__,只让类实例初始化__slots__里面定义的属性,而且让实例不再拥有__dict__方法,来达到节约内存的目的。我将会就上面的那个例子重写一下,来说明这个问题。
"""
遇到问题没人解答?小编创建了一个Python学习交流群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
"""
class Test(object):
__slots__ = ["age"]
fly = True
def __init__(self, age):
self.age = age
output:
In [25]: Test.__dict__
Out[25]:
dict_proxy({"__doc__": None,
"__init__": <function __main__.__init__>,
"__module__": "__main__",
"__slots__": ["age"],
"age": <member "age" of "Test" objects>,
"fly": True})
In [36]: p.__dict__
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-36-3a1cec47d020> in <module>()
----> 1 p.__dict__
AttributeError: "Test" object has no attribute "__dict__"
In [37]: p.age
Out[37]: 3
In [38]: p.fly
Out[38]: True
可以看到,__slots__方法并没有阻止由下至上的属性查找方法,只是不会再允许没有包含在__slots__数组中的属性再被赋值给实例了。但这并不妨碍,继续调用允许访问的属性,以及类属性。
关于__get__, set, del:
在前面的文章里面我也介绍过这三个魔法方法,虽然一般是用不到的,但是在写库的时候它们有特别的用途。他们是python另外一个协议descriptor的根基。
同一个对象的不同属性之间可能存在依赖关系。当某个属性被修改时,我们希望依赖于该属性的其他属性也同时变化。在这种环境下面__dict__方法就无法办到。因为__dict__方法只能用来存储静态属性。python提供了多种即时生成属性的方法。其中一种就是property。property是特殊的属性。比如我们为上面的例子增加一个property特性,使得他能够动态变化。来看这个例子:
class Test(object):
fly = True
def __init__(self, age):
self.age = age
def whether_fly(self):
if self.age <= 30:
return True
else:
return False
def just_try_try(self, other):
pass
whether_fly = property(whether_fly)
p = Test(20)
print p.age
print p.whether_fly
p.age = 40
print p.age
print p.whether_fly
output:
20
True
40
False
可以看到 我们可以使用这种手段,动态修改属性值。property有四个参数。前三个参数为函数,分别用于处理查询特性、修改特性、删除特性。最后一个参数为特性的文档,可以为一个字符串,起说明作用。这里我只是要到了第一个参数,查询的时候动态修改他的返回值,而第二个参数是在修改值的时候就会体现出来。