写在前面
以前手写在笔记本上的知识,这次把这些杂七杂八整理下上云,省的以后笔记本不知道丢哪里。
参考学习:
概述
主要是双下划线方法,是一系列特殊的方法,其名称以两个下划线开头和结尾。
这些方法在Python的类定义中扮演重要角色,允许开发者通过它们来自定义类的行为,使得类能够以更自然的方式与Python的语言特性互动。
一、创建访问类
1、__init__
__init__(self, 参数1, 参数2......)
在创建实例时会立刻执行此方法从,对实例进行初始化,也是最常用的调用方法。
当我们创建一个空白对象后,我们用该方法为这个空白对象增加属于它的独特部分(创建属性值),此方法不需要任何返回值。
2、__new__
__new__(cls, 参数1, 参数2......)
此方法永远为静态方法,且不需要显式装饰(即通常不需要@staticmethod)。
会在__init__方法之前调用,并返回一个空白对象。
第一个参数时创建实例所需的类,其余的参数会传给__init__方法,最后必须返回一个创建好的对象,且一定要有cls。(其返回对象会交给__init__进行初始化,若不是cls对象,则不会触发cls的__init__)
若要重写该方法,也需要调用父类的__new__得到空白对象。
instance = super().__new__(cls)
return instance
但实际上,__new__参数不会影响__init__收集参数(__new__为静态方法,里面执行的操作不会影响对象)。
3、__getattr__
__getattr__(self, attr_name: str)
当访问一个对象不存在的属性时会触发此方法。
若对象不具备某属性,则会触发AttributError异常。
若自定义此方法,则不会报异常。
4、__getattribute__
__getattribute__(self, attr_name: str)
其类似于__getattr__,而此方法是访问属性时都会执行。
其有返回值,返回为对应属性的值,自定义时如不写返回,则永远也得不到对应属性的值。
需要注意的是,其方法内一定不要使用常规方法访问属性,即用【self.属性】,因为此方法会导致死循环(self表示对象,便会进入该对象的__getattribute__,之后再次进入self,最终栈溢出)。
为解决上面的方法,可以这样操作:(其中__name即想要取的属性)
- 调用父类的方法(将被访问属性传进去)
super().__getattribute__(__name)
- 调用基类方法(需要传self指定对象)
object.__getattribute__(self.__name)
梳理访问对象属性的逻辑:
当试图访问一个对象的属性时,必先进入__getattribute__方法。
若无此属性,则会抛出AttributError异常;若自定义重写了__getattr__,则进入此方法。
若有此属性,则返回属性值。
5、__setattr__
__setattr__(self, attr_name: str, attr_value: str)
对一个属性/对象进行赋值时便会调用。
在使用__init__进行赋值时,也会调用此方法。
对不存在的属性赋值是允许的,且赋值后是可以访问的,即__getattribute__不会抛出异常。
其内部赋值也容易死循环,即在__setattr__中用self进行赋值,解决方法与__getattribute__一样。
二、类型转换类
1、__str__
__str__(self)
将对象转化为字符串时调用,必须有一个字符串类型的返回值,在执行str(对象)时调用。
2、__bool__
__bool__(self)
在执行bool(对象)或者是放在if后进行条件判断时调用。
3、__int__、__float__
__int__(self)、__float__(self)
在执行init(对象)或者是float(对象)时执行。
需要注意两方法的返回值需要符合各自的要求。
三、比较操作符类
1、__eq__、__ne__
__eq__(self, other)、__ne__(self, other)
当使用==符号时,__eq__会被调用,会率先调用左边对象的__eq__, 若左边没有,才会调用右边。而__ne__是使用!=时候调用。
两方法都需要返回True or False。
2、__lt__、__le__、__gt__、__ge__
__lt__(self, other)、__le__(self, other)、__gt__(self, other)、__ge__(self, other)
以上方法依次在<、<=、>、>=时候调用。
在进行对象比较排序时,即可以自定义规则(借用列表的sort方法)。
如boy1,boy2,boy3,boy4为四个Boy对象,通过自定义上面的魔法方法,可以比较每个对象的年龄(用列表排序[boy1, boy2, boy3, boy4])
通常不需要全部定义,只要定义__eq__、__lt__、__gt__即可,因为==与!=,<与>=,>与<=互反。
3、一元操作符
__pos__(self) | 数值取正 |
__neg__(self) | 数值取负 |
__invert__(self) | 对象取反 |
4、二元操作符
二元操作符的调用顺序是从左到右,左侧对象的方法优先调用。
在重写该方法时,要保证其可交换性。
对于该类方法,由分为三小类:
1)普通方法
2)即席方法
如+=、-=等,即修改self本身并返回self。
若没有定义即席方法,只定义了普通方法,并不会报错,此时+=等价于x=x.__add__(y),x接收x.__add__(y)的返回值,类型也变为数值型。
3)取反方法
+左边没有提供__add__方法,且两边操作的对象不同,执行右边对象的__radd__方法。
即x+y时,x没提供__add__方法且x,y的类型不同,执行y.__radd__(x)。
操作符 | 普通方法 | 取反方法 | 即席方法 |
+ | __add__ | __radd__ | __iadd__ |
- | __sub__ | __rsub__ | __isub__ |
* | __mul__ | __rmul__ | __imul__ |
/ | __truediv__ | __rtruediv__ | __itruediv__ |
// | __floordiv__ | __rfloordiv__ | __ifloordiv__ |
% | __mod__ | __rmod__ | __imod__ |
** | __pow__ | __rpow__ | __ipow__ |
& | __and__ | __rand__ | __iand__ |
| | __or__ | __ror__ | __ior__ |
^ | __xor__ | __rxor__ | __ixor__ |
<< | __lshift__ | __rlshift__ | __ilshift__ |
>> | __rshift__ | __rrshift__ | __irshift__ |
四、集合类
1、__getitem__
__getitem__(self, index)
当对象使用下标查询时会进入该方法,该方法会收集index,后续如何处理, 自己定义/
该方法只能完成查询操作,不能对下标进行赋值。
2、__setitem__
__setitem__(self, key, value)
若重写此方法,便可以使用中括号+下标进行赋值。
3、__contains__
__contains__(self, ele)
在使用in的时候进行赋值,如 boy1 in boy2,(查询boy1对象是否在boy2对象中)
五、其他类
1、__len__
__len__(self)
调用len(对象)时候会执行,返回对象的长度(返回值需要>=0)。
在没有定义__bool__时,若使用if对象或者bool(对象)时候,也会进入该方法。(0代表flase)
2、__repr__
__repr__(self)
确定对象在终端的显示方式,即print()。
自定义终端的显示方式,否则返回的样式并不友好(<__main__.Object at XXX>)。
对比__repr__(self)与__str__(self):
前者时显示在终端,debug对象时候查看,给程序员看;后者是调用str(对象)时执行,主要给用户看。
3、__hash__
__hash__(self)
在对象传递给散列函数时调用,也就是将数据放入集合时有,在hash表中会调用。
其返回一个整型值,可以是负数,作为一个唯一标识,标识一个对象。
若自定义了__eq__方法,但没有定义__hash__方法,默认的__hash__将会隐式变为None,即无法调用。
例如下面的代码示例:
class MyClass(object):
def __init__(self, age, name):
self.name = name
self.age = age
def __hash__(self):
print(f"{self.name}的__hash__被执行")
return 0
def __repr__(self) -> str:
return f"MyClass(name={self.name}, age={self.age})"
def __eq__(self, other) -> bool:
if self.age == other.age:
return True
else:
return False
boy1 = MyClass(name="小明", age=14)
boy2 = MyClass(name="小华", age=14)
boy3 = MyClass(name="小褚", age=14)
boys = set()
boys.add(boy1)
boys.add(boy2)
boys.add(boy3)
print(boys)
其执行结果为:
小明的__hash__被执行
小华的__hash__被执行
小明的__eq__被执行
小红的__hash__被执行
小明的__eq__被执行
{MyClass (name=小明, age=14)}
相应解释如下:
重写了__hash__和__eq__方法,当年龄相等时,__eq__将返回True,而__hash__则一直返回0。
在set中判断两个对象是否相等,使用的逻辑是: obj1.__hash__() == obj.__hash__() and (obj1.id() == obj.id() or obj1.__eq__(obj2))。
(因为__hash__返回均为0,且年龄一致,所以返回的最后一直是小明,不会替换)
4、__format__
__format__(self, spec_str)
在字符串输出时调用,在“”.format(对象)中执行。
而spec_str参数会保留在{}中的:后面的部分(格式化要求)。如:
“{}, {: .2f}”.format(boy1, boy3)中,spec_str依次为“”,“.2f”。