python魔术方法属性篇
本篇章主要讲与对象的属性有关的魔术方法
3,属性篇
- __getattr__
- __getattribute__
- __setattr__
- __delattr__
- __dir__
- __get__
- __set__
- __delete__
- __slots__
__getattr__方法
每当我们写 形如这种o.test的代码的时候,我们实际上都是在尝试access这个对象的一个属性attribute,__getattr__就是当这个属性不存在的时候,你希望它做点什么,默认情况下,如果你在一个对象里尝试access一个不存在的属性的话,它会raise一个AttributeError。
我们可以看到如果我们没有定义这个__getattr__函数,它会raise这个:AttributeError:'A' object has no attribute 'test‘,
class A: pass # def __getattr__(self, name): # print(f"getting {name}") # raise AttributeError o = A() print(o.test)
定义了之后,我们可以看到这个print被执行了,然后后面我们还是raise了一个AttributeError,这里注意一下,你access的这attribute会以string的形式被传到这个__getattr__函数里,比如这个argument name里面传入的就是string test
class A: def __getattr__(self, name): print(f"getting {name}") raise AttributeError o = A() print(o.test)
那假如说我们希望这个object,当有程序尝试去读取它一个不存在的属性的时候,它永远返回None,我们就可以在这个__getattr__里面返回一个None,这个时候我们运行程序就会发现o.test变成None了
class A: def __getattr__(self, name): print(f"getting {name}") raise None o = A() print(o.test) # getting test # None
这里我们注意一下,这个__getattr__函数,只有在你读取一个它不存在的属性的时候,才会被调用
class A: def __init__(self): self.exist = "abc" def __getattr__(self, name): print(f"getting {name}") raise None o = A() print(o.exist) print(o.test)
# abc
# getting test
# None
__getattribute__方法
__getattrbute__这个方法是只要你尝试去读取它的属性,它都会被调用。同样的情况下,无论我们是打印o.exist还是o.test,它都调用了这个__getattrbute__函数,并且都打印出了None,那当然我们现在这个__getattrbute__函数没什么意义,就是你读取什么属性它都给你返回None
class A: def __init__(self): self.exist = "abc" def __getattribute__(self, name): print(f"getting {name}") raise None o = A() print(o.exist) print(o.test)
# getting exist
# None
# getting test
# None
我们写一个稍微有一点点意义的例子,我们这里这个__getattrbute__函数加了一个逻辑,当access的这个名字是data的时候,我把这个 counter加一个,也就是说我要数一数我这个data属性被读了多少次,这里我们注意一下return super().__getattribute__(name),当你使用__getattribute__的时候,如果你想使用它的default behavior (默认行为),你一定要用super().__getattribute__,这里如果你不小心的话就会产生一个无限的递归
class A: def __init__(self): self.exist = "abc" self.counter = 0 def __getattribute__(self, name): if name == "data": self.counter += 1 return super().__getattribute__(name) o = A() print(o.data) print(o.data) print(o.counter) # abc # abc # 2
比如有人可能会觉得我学过getattr这个函数,我默认返回一个getattr(self, name)不就可以了吗,这样的话就会产生一个无限的递归,因为getattr函数又会返回来调用这个self的__getattribute__函数,同时你也要意识到在这个函数里面,特别容易出现一些不易察觉的递归,比如说self.counter += 1 实际上是一个递归调用,它要读取 self.counter,而当你读取self.counter的时候就调用了这个__getattribute__函数,所以这里面是有一个递归的
class A: def __init__(self): self.exist = "abc" self.counter = 0 def __getattribute__(self, name): if name == "data": self.counter += 1 return getattr(self, name) o = A() print(o.data) print(o.data) print(o.counter)
如果说你想数一数这个object里面所有的属性被读取的次数,然后写了下面这么一个函数,你就会发现它又无限的递归了,因为你每一次加counter的时候,你都要读取一下counter,然后又要加counter,你就一直递归下去了,所以大家在使用这个__getattribute__方法的时候,一定要小心一点,看清里面有可能会产生的不显眼的递归,然后记住它的默认的behavior应该是super()之后再__getattribute__
class A: def __init__(self): self.exist = "abc" self.counter = 0 def __getattribute__(self, name): self.counter += 1 return super().__getattribute__(name) o = A() print(o.data) print(o.data) print(o.counter)
__setattr__方法
当我们尝试去写一个属性的时候,就要用到__setattr__,__setattr__有两个argument,一个是name,一个是value。
当我们做self.data = "abc"的时候,data就作为一个string被放到了name里面,然后“abc”就是这个value,同样的我们一般会使用
super().__setattr__来完成它的默认行为,当然这里你也可以用这个object.__setattr__,包括前面的get,也可以直接使用这个object的默认method,这里可以看到当我们写self.data和写self.counter的时候,这个__setattr__函数都被调用了,同时我们在打印o.data的时候也正确的打印出了“abc”这个string。
class A: def __init__(self): self.data = "abc" self.counter = 0 def __setattr__(self, key, value): print(f"set {key}") super().__setattr__(key, value) o = A() print(o.data)
set data set counter abc
当然你说这个__setattr__包括__getattr__最后一定要用这个super吗?也不是哎,举一个简单的例子,我们看在这个class里面的__setattr__
跟__getattr__,我们不再使用super里面的默认的方法,而是在set的时候把这个name value pair给放到这个class级别的_attr variable里面,也就是在第二行这里我们定义的从属于class的这个dictionary,而在__getattr__的时候,我们直接从这个class的_attr dict里面找到这个value,这里注意,由于我们定义的是__getattr__,而不是__getattribute__,所以我们在尝试读取self.attr的时候并不会递归调用这个函数,因为self._attr是存在的,它可以在自己的class里面找到它,那这样做完的结果就是这个class的所有object,实际上,共享了他们所有自定义的attribute。
我们让o1是A的object,o2也是A的object,这个时候我们把o1的data给写成xyz,然后打印o2的data,可看到o2的data就变成了xyz
class A: _attr = {} def __init__(self): self.data = "abc" def __setattr__(self, key, value): self._attr[key] = value def __getattr__(self, item): if item not in self._attr: raise AttributeError return self._attr[item] o1 = A() o2 = A() o1.data = "xyz" print(o2.data)
# xyz
__delattr__方法
delattr即delete attribute,这个__delattr__和我们之前提到的那个__del__,是不一样的,在一个object正常产生和消亡的过程中,这个__delattr__是不会被调用的,尽管我们在object上面定义了一个属性,但是这个object消失的时候,并不会调用这个__delattr__函数
class A: def __init__(self): self.data = "abc" def __delattr__(self, item): print(f"del {item}") o = A()
这个__delattr__函数是在我们尝试删除一个object属性的时候才会被调用,而删除一个object属性,我们是使用del这个关键词,比如这里我们使用del o.data,这个__delattr__函数就会被调用,当然由于我们这个__delattr__函数里面,只写了一个print,所以在我们尝试del之后,这个o.data它还存在
class A: def __init__(self): self.data = "abc" def __delattr__(self, item): print(f"del {item}") o = A() del o.data
这里我们同样的需要用super()来调用它父类的__delattr__函数,然后最后print(o.data)就会出错
class A: def __init__(self): self.data = "abc" def __delattr__(self, item): print(f"del {item}") super().__delattr__(item) o = A() del o.data print(o.data)
AttributeError: 'A' object has no attribute 'data'
__dir__方法
有关属性我们有一个挺常用的内置函数叫做dir,比如说我们在打印这个dir(o)的时候,它就会把o里面你能access到的一些属性给你列出来,那这里面当然有一些内置的variable和method,也有我们自己定义的这个attribute data
class A: def __init__(self): self.data = "abc" o = A() print(dir(o))
['__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__', 'data']
那我们可以在一个class里面,通过定义这个__dir__这个魔术方法来改变dir这个内置函数的结果,python规定这个dir方法必须要返回一个sequence,那就常见的sequence就是一个list,我们如果在这个方法里面返回一个空list的话,我们在打印dir(o)就是空的了
class A: def __init__(self): self.data = "abc" def __dir__(self): return [] o = A() print(dir(o)) # []
我们可以写一个稍微有意义一点的方法,比如我们拿到它正常的dir,然后排除这个list里面所有下划线开头的attribute,这样我们打印出来就只剩下一个data了
class A: def __init__(self): self.data = "abc" def __dir__(self): lst = super().__dir__() return [el for el in lst if not el.startswith("_")] o = A() print(dir(o)) # ['data']
__get__方法
接下来我们看几个与descriptor(描述器)有关的magic method。
首先class D我们定义了一个descriptor,然后在class A里面,这个attribute x是一个D的object,我们一般会说x 是一个描述器descriptor,
我们建立了一个A的object o,然后打印o.x,我们注意一下这个descriptor里面定义的__get__函数,这个__get__函数,会在我们尝试读取o.x的时候被调用,而__get__函数有两个argument,注意一下这里很容易搞混,self是这个descriptor object本身,也就是x,然后这个的obj有时候写成instance,对应的是这个o,是这个class A的object,也就是在我们读取o.x的时候,o会被作为这个object传进来,这个owner是o的class,我们运行一下可以看到,打印出来的obj是一个A的object,也就是o,而这个owner是这个class A,同时我们在这个__get__ method里面返回了0,所有o.x就是0
class D: def __get__(self, obj, owner=None): print(obj, owner) return 0 class A: x = D() o = A() print(o.x)
<__main__.A object at 0x0000020BC3E84850> <class '__main__.A'> 0
__set__方法
在我们尝试写一个descriptor object的时候,就会调用到这个__set__函数,我们把这个descriptor类稍微改了一下,我们让每一个这个类的object有一个val,然后写的时候呢写这val,读的时候也读这个val,这样我们实际上就是在模仿一个真实的attribute行为,我们在建立一个object之后,先打印o.x,然后把o.x写成1,再打印o.x,这里的结果就是0 1
class D: def __init__(self): self.val = 0 def __get__(self, obj, owner=None): return self.val def __set__(self, obj, value): self.val = value class A: x = D() o = A() print(o.x) o.x = 1 print(o.x) # 0 # 1
当然这里注意一下,descriptor这个东西是class level的,所以如果我们有另一个A的object o2,它的x也会被这个o.x写到,也就是通过这个descriptor我们实现了一个刚才我们用__getattr__跟__setattr__实现的功能
class D: def __init__(self): self.val = 0 def __get__(self, obj, owner=None): return self.val def __set__(self, obj, value): self.val = value class A: x = D() o = A() o2 = A() print(o.x) o.x = 1 print(o.x) print(o2.x)
__delete__方法
当我们使用这个del o.x时候,会调用这个__delete__函数
class D: def __init__(self): self.val = 0 def __get__(self, obj, owner=None): return self.val def __set__(self, obj, value): self.val = value def __delete__(self, obj): print("delete") class A: x = D() o = A() del o.x
# delete
__slots__方法
__slots__它不是一个魔术方法,它压根就不是一个方法,但是它也算一个special names,它是有特殊的含义的,简单的说,它就是规定了这个class的object里面可以有哪些自定义的attribute,它是一个白名单机制,比如下面我们只允许让这个A的object里面,有a跟b这两个attribute,当我们在尝试做o.x = 1的时候,它就会报错,那如果我们做o.a = 1它就没事
class A: __slots__ = ['a', 'b'] o = A() o.a = 1 o.x = 1
AttributeError: 'A' object has no attribute 'x'
标签:__,python,self,魔术,print,data,class,def,属性 From: https://www.cnblogs.com/jiushao-ing/p/17555367.html