python 魔术方法
前言
在做python开发的过程中,我们大家都会遇到在class(类)中使用双下划线的方法,这些都是我们经常所说的”魔法”方法.这些方法可以对类添加特殊的功能,使用恰当可以很大的提升我们在开发过程中的便捷性,方便的进行扩展.
概览
目前我们常见的魔法方法大致可分为以下几类:
- 构造与初始化
- 类的表示
- 访问控制
- 比较操作
- 容器类操作
- 可调用对象
- Pickling序列化
我们这次主要介绍这几类常用魔法方法:
1.构造与初始化
__init__
构造方法是我们使用频率最高的魔法方法了,几乎在我们定义类的时候,都会去定义构造方法,它的主要作用就是在初始化一个对象时,定义这个对象的初始值。
1 | class Person(object): |
__new__
- 事实上,当我们理解了new方法后,我们还可以利用它来做一些其他有趣的事情,比如实现 设计模式中的 单例模式(singleton)
- 依照Python官方文档的说法,new方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass
- 这个方法我们一般很少定义,不过我们在一些开源框架中偶尔会遇到定义这个方法的类。实际上,这才是”真正的构造方法”,它会在对象实例化时第一个被调用,然后再调用init,它们的区别主要如下:
- new的第一个参数是cls,而init的第一个参数是self
- new返回值是一个实例,而init没有任何返回值,只做初始化操作
- new由于是返回一个实例对象,所以它可以给所有实例进行统一的初始化操作
- 由于new优先于init调用,且返回一个实例,所以我们可以利用这种特性,每次返回同一个实例来实现一个单例类:
__new__的作用:
1 | class PositiveInteger(int): |
但运行后会发现,结果根本不是我们想的那样,我们仍然得到了-3。这是因为对于int这种不可变的对象,我们只有重载它的new方法才能起到自定义的作用。
修改后的代码如下:
1 | class PositiveInteger(int): |
通过重载new方法,我们实现了需要的功能.
1 | class g(float): |
用new来实现单例
因为类每一次实例化后产生的过程都是通过new来控制的,所以通过重载new方法,我们 可以很简单的实现单例模式。
1 | # 写法一 |
2. del析构方法
这个方法代表析构方法,也就是在对象被垃圾回收时被调用。但是请注意,执行del x不一定会执行此方法。
由于Python是通过引用计数来进行垃圾回收的,也就是说,如果这个实例还是有被引用到,即使执行del销毁这个对象,但其引用计数还是大于0,所以不会触发执行del。
例子:
此时我们没有对实例进行任何操作时,del在程序退出后被调用。
1 | class Person(object): |
由于此实例没有被其他对象所引用,当我们手动销毁这个实例时,del被调用后程序正常退出。
1 | class Person(object): |
此时实例有被其他对象引用,尽管我们手动销毁这个实例,但依然不会触发del方法,而是在程序正常退出后被调用执行。
为了保险起见,当我们在对文件、socket进行操作时,要想安全地关闭和销毁这些对象,最好是在try异常块后的finally中进行关闭和释放操作!
3. 类的表示
str/repr
这两个魔法方法一般会放到一起进行讲解,它们的主要差别为:
str强调可读性,而repr强调准确性/标准性
str的目标人群是用户,而repr的目标人群是机器,它的结果是可以被执行的
%s调用str方法,而%r调用repr方法
来看几个例子,了解内置类实现这2个方法的效果:
1 | >>> a = 'hello' |
从上面的例子可以看出这两个方法的主要区别,在实际中我们定义类时,一般这样定义即可:
1 | class Person(object): |
这里值得注意的是,如果只定义了str或repr其中一个,那会是什么结果?
如果只定义了str__,那么repr(person)输出<_main.Person object at 0x10783b400>
如果只定义了repr,那么str(person)与repr(person)结果是相同的
也就是说,repr在表示类时,是一级的,如果只定义它,那么str = repr。
而str展示类时是次级的,用户可自定义类的展示格式,如果没有定义repr,那么repr(person)将会展示缺省的定义。
4. 对象判断
hash/eq
hash方法返回一个整数,用来表示该对象的唯一标识,配合eq方法判断两个对象是否相等(==):
1 | class Person(object): |
如果我们需要判断两个对象是否相等,只要我们重写hash和eq方法就可以完成此功能。此外使用set存放这些对象时,会根据这两个方法进行去重操作。
5. 对象布尔判断
bool
当调用bool(obj)时,会调用bool方法,返回True/False。
1 | class Person(object): |
⚠️: 在Python3中,nonzero被重命名bool
6. 访问控制
访问控制相关的魔法方法,主要涉及以下几个:
setattr:通过.设置属性或setattr(key, value)
getattr:访问不存在的属性
delattr:删除某个属性
getattribute:访问任意属性或方法
来看一个完整的例子:
1 |
|
- setattr
通过此方法,对象可在在对属性进行赋值时进行控制,所有的属性赋值都会经过它。
一般常用于对某些属性赋值的检查校验逻辑,例如age不能小于0,否则认为是非法数据等等。 - getattr
很多同学以为此方法是和setattr完全对立的,其实不然!
这个方法只有在访问某个不存在的属性时才会被调用,看上面的例子,由于gender属性在赋值时,忽略了此字段的赋值操作,所以此属性是没有被成功赋值给对象的。当访问这个属性时,getattr被调用,返回unknown。 - del
删除对象的某个属性时,此方法被调用。一般常用于某个属性必须存在,否则无法进行后续的逻辑操作,会重写此方法,对删除属性逻辑进行检查和校验。 - getattribute
这个方法我们很少用到,它与getattr很容易混淆。它与前者的区别在于:
getattr访问某个不存在的属性被调用,getattribute访问任意属性被调用
getattr只针对属性访问,getattribute不仅针对所有属性访问,还包括方法调用
越是强大的魔法方法,责任越大,如果你不能正确使用它,最好还是不用为好,否则在出现问题时很难排查!