Python之魔术方法
[TOC]
什么是魔术方法
定义
魔术方法,官方名称是special methods,因为带有双下划线,又被称为dunder methods双下划线方法。
当我们创建一个自定义类时,往往需要控制这个类的行为和操作。通过定义和实现这些魔术方法,我们可以在自定义类中实现类似于内置类型的行为和功能,使得我们的类更加灵活和易于使用。
示例
__init__
用于在创建对象时进行初始化操作。它在对象创建之后立即被调用。
1 | class Person: |
__init__
方法的第一个参数通常被命名为self,它代表正在被创建的对象自身。接下来的参数表示我们在创建对象时传递的参数。通过这些传递的参数,我们可以在初始化方法中对对象的属性进行赋值。
魔术方法类别
比较
符号 | 方法 | tips |
---|---|---|
== | __eq__ |
equal,无定义时,默认使用is逻辑。 |
!= | __ne__ |
not equal,无定义时,把eq函数取反 |
> | __gt__ |
greater than,无定义时,取反、报错 |
< | __lt__ |
less than,无定义时,取反、报错 |
>= | __ge__ |
greater equal,无定义时,取反、报错 |
<= | __le__ |
less equal,无定义时,取反、报错 |
取反,当==方法被定义,而!=方法没有定义时,调用!=方法,会使用==方法,并将结果取反。其他同理。
一般情况下,当符号是<时,调用
__lt__
方法,但也有例外,例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class Date():
def __init__(self, year, month, date):
self.year = year
self.month = month
self.day = day
def __gt__(self, other):
...
def __lt__(self, other):
if self.year < other.year:
return True
if self.year == other.year:
if self.month < other.month:
return True
if self.month == other.month:
return self.day < other.day
return False
class NewDate(Date):
pass
date1 = Date(2022,10,1)
date2 = NewDate(2023,10,1)
print(date1 < date2)
# 正常情况下,date1.__lt__(date2),调用lt函数,但这里的date2是子类,因为有可能被重写,所以实际上调用的是date2.__gt__(date1)
# 这里的规则是,两边是不同类且是衍生类关系,首先使用衍生类的方法。两边是不同类且无衍生关系的时候,我们首先使用左边类的方法,如果左边没有lt方法,则将date1 < date2,视为date2 > date1,调用date2的gt方法,如果这两者都没有,就会报错,python认为两边没有定义date1 < date2的比较逻辑,不可比较。<=方法不等于<和==方法,即使写了后两者,没写前者也用不了<=方法。
hash值,当我们自定义一个数据结构时,是有默认的
__hash__
方法的,如果我们定义了__eq__
函数,那么默认的hash方法会被删除。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47# 解释一下hash函数
# 首先hash函数有关于可变对象和不可变对象
# 如果一个对象是不可变的,那么它的__hash__()方法应该返回一个固定的哈希值,并且__eq__()方法应该比较对象的内容来判断相等性。
# 如果一个对象是可变的,那么它的__hash__()方法通常不会被实现(默认返回None),并且对象的相等性比较通常会比较对象的身份(即id())而不是内容。
# 例如:
a = 1
b = 1
# int类型是不可变对象,所以a is b结果为true,这里不要认为a、b是两个不同的对象,因为在编程中,a和b都是引用,指向同一块内存。hash值用来标识唯一数据对象,a和b都是同一块内存,输入相同,输出的hash值也一样。
# 自定义的数据对象例如:
class Date():
...
date1 = Date(2022,10,1)
date2 = Date(2022,10,1)
# date1 is date2的时候就会返回False,因为两个变量指向的是两块不同的内存。得出的hash值也不同。
# 当定义了__eq__方法,hash方法就不存在,从常理来讲,我们认为date1和date2相等,是因为两个变量都指向2022/10/1这个日期,所以自定义的Date类型应该和int类型一样,都是不可变对象,但由于是自定义对象,date1和date2实际上是指向两块不同的内存,而hash值是确保对象的唯一标识的手段,也就是说,如果两个对象的哈希值不同,那么它们的对象肯定不相等。但如果两个对象的哈希值相同,系统会继续比较它们的值或调用 __eq__() 方法来确定它们是否相等。
# 这就好比两个双胞胎,明明是两个不同的肉体(内存),却有着一样的外形(eq),你分辨不出,所以你通过名字标识了他们(hash),你就能知道是两个不同的个体。如果是两个名字一样的人(hash相同),但是外形不同(eq),那么你也知道是两个人。hash帮助了你在分别见到双胞胎的时候,叫名字不至于认错,这就是唯一标识。唯一标识的作用下面解释。
# 用处
# 在字典中,key值必须唯一,不能重复,这是为了快速索引。但Date创建的date1和date2对象是个双胞胎,如果在字典中:
income = {} # 收入
income[date1] = 100
income[date2] = 100
print(income)
'''{2022/10/1: 100, 2022/10/1: 100}'''
# 但我们要的是一个date
# 所以可以在Date类中定义hash和eq方法,系统就会知道他俩相等。
def __eq__(self, other):
return self.year == other.year and self.month == other.month and self.day == other.day
def __hash__(self):
return 1 #例子,别学
'''{2022/10/1: 100}'''
# 这样就标识了2022/10/1这个日期是唯一的,日期就是不可变对象
# 总结
# 对于不可变对象(如整数、浮点数、字符串和元组),它们的哈希值是在对象创建时计算并保存的,因为它们的值是不可变的。这使得它们的哈希值唯一性和稳定性得到了保证。
# 对于可变对象(如列表、集合和字典),由于其值可以进行修改,因此在对象创建后即使不同的修改操作会改变其哈希值,因此可变对象默认是不可哈希的,即不能被用作字典的键或集合的元素。
# 尽管可变对象默认是不可哈希的,但可以通过自定义对象的 __hash__() 方法来使可变对象变为可哈希的。但一旦对象是可哈希的,并且作为字典键或集合元素使用时,就不应该再修改该对象的值,因为修改后会破坏数据结构的一致性。
# hash函数要求
# 必须返回一个整数
# 两个对象相等的时候,hash值必须相等
# 官方推荐写法
def __hash__(self):
return hash((self.year,self.month,self.day))
对象表示
方法 | tips |
---|---|
__str__(self) |
返回对象的字符串表示 |
__repr__(self) |
represent,返回对象的可打印字符串表示 |
区别:
str面向用户,要求可读性好。repr面向的是python的解释器,或者说开发人员,用来重新获得该对象,将对象转化为供解释器读取的形式。
示例:
1 | class Point: |
属性
方法 | 作用 | tips |
---|---|---|
__getattr__(self,name) |
访问对象某个属性不存在时,做的处理 | 不存在指getattribute方法返回值没有 |
__getattribute__(self,name) |
访问对象属性存在时,返回属性值 | 可以加一写其他操作,注意递归 |
__setattr__(self,name,val) |
设置一个对象属性 | 无 |
__delattr__(self,name) |
del o.data对象属性时被调用 | 对象删除时并不会调用 |
__dir__(self) |
打印可访问的属性和方法 | 必须返回sequence |
__getattr__(self,name)
1 | class Person: |
__getattribute__(self,name)
1 | class A(): |
描述器
方法 | 作用 | tips |
---|---|---|
__get__(self, instance, owner) |
通过实例访问属性时调用,定义属性的获取行为 | |
__set__(self, instance, owner) |
给属性赋值时调用,定义属性的设置行为 | |
__delete__(self, instance, owner) |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 一个正常的人!
评论