Python类和对象进阶¶
约 2812 个字 268 行代码 预计阅读时间 13 分钟
限制属性绑定¶
因为Python属于动态类型语言,所以允许创建一个没有任何属性和方法的类,在创建该类的实例后使用对象单独添加属性和方法,例如下面的代码:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
如果使用这种方式添加方法,那么这个方法就属于单个对象而不是所有对象共有,所以一般使用这种方式添加方法会考虑给类添加方法:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
但是,这种添加属性的方式并不会限制对象总共有哪些属性,也就是说,对象可以创建任意的属性。为了限制这种行为,在Python中可以在类中使用__slots__()
属性,其值为一个存储着本类对象可以添加的属性的元组,例如下面的代码:
Python | |
---|---|
1 2 3 |
|
此时Person
类对象就只能添加name
和age
属性,例如下面的代码:
Python | |
---|---|
1 2 3 4 5 6 7 8 |
|
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,也就是说子类对象可以添加__slots__
指向的元组中不存在的属性:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
使用@property
装饰器¶
前面在类和对象基础:封装性中提到为了满足封装性需要提供getter
和setter
方法,而在通过对象获取对应的属性以及设置对应的属性就需要调用这两个方法,这个过程就会存在割裂感。为了简化这个过程,就可以使用@property
装饰器,该装饰器的作用就是将原来通过方法调用获取对应的属性的方式转换为正常访问属性的方式(即对象.属性名
),将原来通过方法调用设置对应的属性的方式转换为给属性赋值的方式(即对象.属性名 = 值
)
例如下面的代码:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
此时使用时,就只需要使用常规方式即可:
Python | |
---|---|
1 2 3 4 5 |
|
在上面的代码中,使用@property
给类添加getter
方法,两个方法分别获取到类的保护属性_name
和_age
,使用属性名(不带下划线).setter
给类添加setter
方法,分别设置类的两个保护属性。这两种拥有了装饰器的方法的方法名和属性名相同
需要注意,getter
和setter
的使用是在有保护属性或者私有属性的情况下使用,一般在属性是开放的时候不使用,而上面说到「两种拥有了装饰器的方法的方法名和属性名相同」,如果属性不是保护的也不是私有的,那么此时方法名就和属性名相同,此时再使用@property
就会出现循环递归调用最后导致栈溢出
多重继承¶
本部分在本文档中不会具体介绍,详细内容见官方教程
魔法方法¶
介绍¶
在Python中,魔法方法是类中的一种特殊方法(special method),这些特殊方法的最大特点就是方法名前后都有__
,下面介绍常用的魔法方法:
-
基础魔术方法
__new__()
方法__del__()
方法__str__()
方法
-
比较魔术方法
__eq__()
方法:类似与C++的operator==
__ne__()
方法:类似于C++的operator!=
__gt__()
方法:类似于C++的operator>
__lt__()
方法:类似于C++的operator<
__ge__()
方法:类似于C++的operator>=
__le__()
方法:类似于C++的operator<=
__hash__()
方法:决定如何计算当前类对象的hash
值__bool__()
方法:决定bool(对象)
的结果,类似于C++的operator bool()
-
算术运算魔术方法
__add__()
方法:类似于C++的operator+
__sub__()
方法:类似于C++的operator/
__mul__()
方法:决定两个同类对象相乘的行为__truediv__()
方法:决定一个对象除以另外一个同类对象的行为__floordiv__()
方法:决定一个对象整除以另外一个同类对象的行为__mod__()
方法:决定一个对象对另外一个同类对象取模的行为
-
__len__()
方法:当调用len(对象)
时会调用该对象的__len__()
方法
基础魔术方法¶
__new__()
方法¶
对于__new__()
方法来说,其与__init__()
方法很类似,都是在创建对象时调用,但是更具体来说,__new__()
是创建对象,即从内存中为对象申请空间,而__init__()
方法是初始化对象中的内容
Note
注意,因为__new__()
是类方法,所以其第一个参数一定是cls
例如下面的代码演示了实例化一个对象时__new__()
和__init__()
都会被调用:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
在上面的代码中可以看到,__new__()
方法对比__init__()
方法,除了参数的不同外,还有返回值的不同,__init__()
方法默认是没有返回值的,但是__new__()
必须写返回值,并且返回调用其父类的__new__()
方法的返回值,因为创建对象的本质就是创建object
类对象,再根据其他内容进行对对象进行初始化
实际上,上面的两个方法执行步骤是:
Python | |
---|---|
1 2 |
|
如果是有属性的对象,那么在__new__()
方法中和__init__()
方法中都需要写上参数,不可以只给__new__()
方法或者__init__()
方法设置参数,例如下面的代码:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
在上面的代码中,假定类Person
有个x
属性,那么为了在创建对象时可以指定对象属性值,就需要通过实参传递给形参,此时就必须要有个形参进行接收,这里假定就是x
(形参),此时在创建Person
类对象时,就等价于下面的步骤:
Python | |
---|---|
1 2 |
|
但是,一般情况下一个类只需要写__init__()
,而不需要写__new__()
,既然如此,__new__()
方法的意义何在?实际上,__new__()
在例如需要判断该类当前已经创建了多少个实例可以使用,比如使用__new__()
实现单例模式:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
__del__()
方法¶
在Python中,__del__()
是一个特殊的方法,也称为析构函数。它的主要目的是定义当一个对象即将被垃圾回收时应该执行的操作。当Python的垃圾收集器检测到某个对象不再有引用,并决定销毁它以释放资源时,就会调用这个对象的__del__()
方法
但是由于垃圾收集的时间点是不确定的,因此__del__()
方法的具体执行时间也是不可预测的,这使得它不适合那些需要立即执行的任务
下面是一个使用__del__()
方法的例子:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
__str__()
方法¶
在Python中,__str__()
是一个特殊方法(也称为魔术方法或双下划线方法),它定义了当使用str()
函数或print()
函数打印对象时应该返回的字符串表示形式。这个方法的主要目的是提供一个对用户友好的、可读性强的对象描述
下面是__str__()
方法使用示例:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
在Python中,有一个魔法方法__repr__()
与__str__()
非常类似,但是__repr__()
更多用于开发者的调试目的,期望得到一个更详细的、准确的表示,以便于开发者理解对象的具体状态,在调用repr()
函数会调用类的__repr__()
方法
比较魔术方法¶
__eq__()
方法¶
其他用于比较的魔术方法(不包括__hash__()
和__bool__()
)和__eq__()
非常类似,不再一一举例,下面以__eq__()
为例具体解释
在Python中,如果一个类实现了__eq__()
方法就可以使用==
进行类对象是否相等判断,例如下面的代码:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
在上面的代码中,__eq__()
方法有两个参数,第一个参数代表当前对象(赋值运算符左侧对象),第二个参数代表其他同类对象(赋值运算符右侧对象),对二者进行相等比较的逻辑就是比较年月日是否都想吐
注意,如果一个类没有重写__eq__()
方法,则使用==
时默认就是使用is
进行判断
与__eq__()
方法相对的就是__ne__()
方法,但是如果不想写__ne__()
也可以,因为Python会根据类的__eq__()
结果进行取反,这个规律同样也适用于小于和大于以及小于等于和大于等于
Note
对于剩余的比较魔术方法就没有像__eq__()
方法一样有默认的比较方式,所以如果当前类没有写其他比较魔术方法就会报错
需要注意,如果比较的两个对象存在继承关系,则默认调用的是子类的比较魔术方法,如果子类没有,则调用父类的比较魔术方法,但是self
是子类对象,而不是父类对象,例如下面的代码:
Python | |
---|---|
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 |
|
__hash__()
方法¶
在Python中,__hash__()
是一个特殊方法,用于定义对象的哈希值。哈希值是一个整数,它由对象的内容生成,并且对于相同内容的对象应该是相同的。哈希值主要用于实现快速查找和比较,特别是在字典(dict
)和集合(set
)等数据结构中。这些数据结构依赖于哈希值来高效地存储和检索元素
在Python中,默认可以使用hash()
函数计算指定的类对象的哈希值,但是如果当前类定义了__eq__()
方法,那么hash()
就会失效,此时就需要重写__hash__()
方法,本质就是因为写了__eq__()
方法改变了默认hash()
的行为
注意,实现__hash__()方法需要遵循下面两点:
__hash__()
必须返回一个整数- 对于两个相同的对象来说,它们必须具有相等的哈希值
根据Python官方文档的建议,可以将对象的每一个属性组成一个元组,再将这个元组作为hash()
函数的参数
下面是使用__hash__()
方法的一个例子:
Python | |
---|---|
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 |
|
__bool__()方法
¶
在 Python 中,__bool__()
是一个特殊方法,它定义了当对象被用作布尔上下文时的行为。这个方法应该返回True
或False
。如果一个类没有定义__bool__()
方法,Python 会尝试调用__len__()
方法,并且如果__len__()
返回 0,则对象被视为False
;否则,对象被视为True
。如果 __len__()
也没有定义,那么对象默认为True
例如下面的代码:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
算术运算魔术方法¶
涉及到算术运算魔法方法的根据前面的使用经验就可以直接看官方文档对这些魔法方法解释了,这里就不再赘述了
__len__()
方法¶
在Python中,__len__()
是一个特殊方法,它定义了当使用内置函数len()
来获取对象的长度时应该返回什么。这个方法通常用于容器类型(如列表、字典、集合等)中,以返回容器中元素的数量。如果一个类实现了__len__()
方法,那么该类的实例就可以被len()
函数处理。一般情况下,__len__()
方法应当返回一个非负整数,表示对象中元素的数量。如果对象不包含任何元素,应返回0。
例如下面的代码:
Python | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|