Python高级编程学习笔记(一) 魔术方法

本文介绍了Python高级编程中的魔术方法,包括__init__、__new__、__del__、__str__、__repr__、二元比较、二元操作符重载等,阐述了它们在面向对象编程中的作用和使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在看Luke Sneeringer的 "Professional Python" 这本书,做一些关于这本书的笔记。

首先是魔术方法,本书的第二部分,第四章。为什么不按顺序来呢,我也不知道。

0. 什么是魔术方法

简单地说,就是在面向对象编程时,编写的一些对于特定操作(例如特定函数、操作符)做出特定响应的函数(作者称之为“钩子”)。

对于魔术方法,遵循统一的格式:双下划线在函数名两端,例如常见的__init__,可以读作“dunder init”。

1. 常用的魔术方法

(1)__init__

在类的实例被创建时立即执行,必须要有参数self,没有也不能有返回值(否则会报错TypeError)。

例如:

class MyClass:
    def __init__(self):
        print("You create an object.")

cls = MyClass()

输出:

You create an object.

其实__init__更多被运用于为初始化对象赋值,但该方法并不创建对象(由__new__方法创建对象)。其作用很像C++中的构造函数。

 

(2)__new__

__new__的应用场景相对来说少一些,简单地说,其主要运用于想要继承一些不可变的自带类(如tuple、str、int等等)时,需要改写父类的__new__方法。

两个例子:

编写一个类,永远返回一个非负整数:

class PositiveInt(int):
    def __new__(cls, val):
        return super(PositiveInt, cls).__new__(cls, abs(val))

编写一个类,永远返回一个大写字母串:

class UpperStr(str):
    def __new__(cls, s):
        return super(UpperStr, cls).__new__(cls, s.upper())

此外,__new__方法还能用于实现单例(Singleton)和元类(MetaClass),属于更高阶的内容,在这里不细讲了。

 

(3)__del__

顾名思义,__del__方法在一个实例被销毁时调用,类似于C++中的析构函数,无论是GC自动销毁实例还是手动使用del关键字时,该方法都会被调用。

这个方法被用到的场景不多,因为该方法通常是由GC触发的,并没有一种很好的方法可以引发有意义的异常,一般只用于在标准输出窗口打印一些错误信息。

 

(4)强制类型转换:__str__、__int__、__bool__

先说__str__(以及容易混淆的__repr__),这应该是最常用的魔术方法,用于把一个实例转化为一个字符串,调用时机:显式调用str函数、调用print函数,使用格式化字符串%s。

例子:

class People:
    def __init__(self, name='null', age=None):
        self.name = name
        self.age = age

    def __str__(self):
        return "My name is %s. I'm %d years old."%(self.name, self.age)

在命令行中调用:

me = People('Tom', 18)
print(me)
print(str(me))

都会输出:

My name is Tom. I'm 18 years old.

调用:

print("Introduction: %s"%me)

输出:

Introduction: My name is Tom. I'm 18 years old.

但是直接调用:

me

输出:

<__main__.People at 0x1b651d048d0>

这是由于在命令行中直接调用实例本身,会调用__repr__方法,而该方法默认返回实例在内存中的地址,下面再说说这个方法,同样是返回一个字符串,与__str__的区别在于:

调用时机:__repr__的调用时机为:直接在命令行中调用实例本身(如上),显式调用repr函数,以及使用%r格式化输出。

此外,可以将__repr__看作__str__的“备胎”,即实例不存在__str__方法时,将会默认调用__repr__方法,因此编写一个类时应该至少编写一个__repr__方法。

两者的区别除了调用时机,还有就是用途,一般__str__输出的是易于阅读的字符串,以自然语言风格为主,而__repr__输出的则是易于调试的字符串,例如可以令其返回XML格式的描述等等。

例子:

class People:
    def __init__(self, name='null', age=None):
        self.name = name
        self.age = age

    def __str__(self):
        return "My name is %s. I'm %d years old."%(self.name, self.age)

    def __repr__(self):
        return "<People>\n<name>%s</name>\n<age>%d</age>\n</People>"%(self.name, self.age)

可以看出,给People重写了__repr__方法,令其返回一段XML格式的描述。

直接调用:

me

返回:

<People>
<name>Tom</name>
<age>18</age>
</People>

调用:

print("Introduction:\n %r"%me)

返回:

Introduction:
 <People>
<name>Tom</name>
<age>18</age>
</People>

其余的:__int__、__bool__比较简单,不再赘述了,同样是接收一个位置参数(self),返回一个对应类型的值。

 

(5)二元比较:此类魔术方法需要两个位置函数,即self和other

判等:__eq__,相当于重载==运算符

例如上面的例子,编写__eq__方法:

    def __eq__(self, other):
        return self.name == other.name

即认为两个人名字相同就相等(返回True),否则返回False

判不等:__ne__,相当于重载!=运算符,一般无需定义,因为只需要对__eq__的结果取反即可,如果不希望直接对__eq__的结果取反,才需要定义__ne__方法。

判小于:__It__,相当于重载<运算符,定义和使用方法和上面很像,不再赘述。

其他二元比较运算符:__Ie__相当于<=, __gt__相当于>,__ge__相当于>=,但是只要定义了__eq__和__It__,解释器就可以推出这三种方法的返回值,因此无需显式定义。

此外,定义了二元比较方法,就可以对该对象组成的列表使用sorted方法。

 

(6)重载二元操作符

对于每一个二元操作符,Python定义了3种魔术方法:即普通(vanilla)方法、取反(reverse)方法和原地(in-place)方法。

普通方法:例如:x.__add__(y)相当于x+y。

取反方法:若x没有定义__add__方法,则x+y调用y.__radd__(x)。即在普通方法前面加上'r'。

原地(自操作)方法:也就是常见的自增、自减等等。x.__iadd__(y)相当于x+=y。即在普通方法前面加上'i'。

下面是常见的二元操作符对应的魔术方法。

 

(7)重载一元操作符:+、-和~

不再赘述,x.__pos__相当于+x,x.__neg__相当于-x,x.__invert__相当于~x。

 

(8)其余常用方法的重载

1)__len__方法

重载len函数,接收一个位置参数,返回一个非负整数值,用于描述对象的长度。

2)__hash__方法

重载hash函数,接收一个位置参数,返回一个整型值(可以为负),返回能唯一标识该对象的id值。

通常用于使一个类能够定义相等且要使其可哈希化的时候,为什么要使一个类的对象可哈希化呢?仅有可哈希化的对象可以作为字典(dict)的键值,以及可以在集合(set)中存在。在字典中,哈希值用于键查找,而在集合中,哈希值用于确定一个对象是否是集合中的成员。

3)__abs__与__round__方法

顾名思义,相当于重载abs和round函数,分别返回绝对值以及取整值。

4)__contains__方法:相当于重载 in 关键字

常用于判断一个对象与另一个对象是否属于”成员关系“,可以用于判断一个对象的值是否处于某个区间。

5)__getitem__、__setitem__、__delitem__方法

用于对象是一个集合(如字典、列表)的情况。

x.__getitem__(key)相当于x[key]

x.__setitem__(key, value)相当于x[key] = value,在执行上述语句的时候自动调用

x.__delitem__(key)则在执行del x[key]的时候被调用。

6)__getattr__, __setattr__, __hasattr__方法

__hasattr__用于查找对象是否包含某个属性,其中要查找的attr_name参数是字符串类型。

__getattr__用于查找对象的某个属性的值,并且在使用常规方法(即成员运算符".")无法找到属性时才会被调用。

__setattr__用于设置对象的某个属性的值,与getattr不同的是,只要显式定义了__setattr__方法,该方法就会被一直调用。

此外,还有__getattribute__方法,与__getattr__方法的区别在于,该方法是无条件被调用的。只有在该方法调用时引发了AttributeError异常时,才会调用__getattr__方法。

7)__iter__方法和__next__方法

这两个方法用于可迭代对象,用于定义该对象的迭代协议。在生成器那一章会讲到,这里不再叙述。

8)__enter__方法和__exit__方法

这两个方法用于上下文管理器,用于定义一个上下文管理器在生命周期开始时和结束时的行为。在上下文管理器那一章会讲到,这里不再叙述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值