Python 魔法方法如何使用魔法方法编写更好的 Python 类
什么是 dunder 方法?
通过示例学习
基本 TimePeriod 类
更好的 TimePeriod 类
添加更多功能
其他有用的魔法方法
综上所述
参考文献和进一步阅读
确保代码清晰易读至关重要,这样即使不熟悉代码库的人也能轻松理解其功能。处理面向对象的 Python 代码时,使用双下划线方法(也称为魔法方法)是实现此目标的有效方法之一。它们允许我们的用户定义类使用 Python 的内置和原始结构(例如+
、*
、/
等运算符以及诸如new
or 之类的关键字len
),这些通常比类方法链更加直观。
什么是 dunder 方法?
如果你使用 Python 编程已有一段时间,那么你很可能遇到过一些名称以双下划线开头和结尾的奇怪方法。如果你使用过 Python 类,你甚至可能熟悉一个特定的方法:__init__
,它充当构造函数,在类初始化时被调用。
__init__
是 dunder(双下划线)方法的一个示例,也称为魔术方法。它们提供了一种方法,让我们可以“告诉”Python 如何处理自定义类上的原始操作,例如加法或相等性检查。例如,当您尝试使用“+”运算符添加两个自定义类的实例时,Python 解释器会进入类的定义内部,查找__add__
魔术方法的实现。如果已实现,它将执行其中的代码,就像任何其他“常规”方法或函数一样。
通过示例学习
让我们实现一个“时间段”类,并看看如何使用 Python 的魔法方法使其更清晰易读。为了简单起见,我们的类只处理小时和分钟,并提供一些基本功能,例如添加和比较时间段。
基本 TimePeriod 类
这个类的基本实现可能看起来像这样:
class TimePeriod:
def __init__(self, hours=0, minutes=0):
self.hours = hours
self.minutes = minutes
def add(self, other):
minutes = self.minutes + other.minutes
hours = self.hours + other.hours
if minutes >= 60:
minutes -= 60
hours += 1
return TimePeriod(hours, minutes)
def greater_than(self, other):
if self.hours > other.hours:
return True
elif self.hours < other.hours:
return False
elif self.minutes > other.minutes:
return True
else:
return False
然后我们可以像这样创建类的实例:
time_i_sleep = TimePeriod(9, 0)
time_i_work = TimePeriod(0, 30)
并对它们执行如下操作:
print(time_i_sleep.greater_than(time_i_work)) # Should print True
这段代码功能上没有任何问题,完全符合预期,没有任何 bug。但是,如果我们想执行更复杂的操作,比如比较两个时间段的总和与另外两个时间段的总和,该怎么办呢?
time_i_sleep.add(time_i_watch_netflix).greater_than(time_i_work.add(time_i_do_chores))
嗯,看起来不太好,不是吗?读者必须深入阅读每个字才能理解代码的作用。
聪明的开发者可能会把这两个和拆成两个临时变量,然后再进行比较。这确实能稍微提升代码的清晰度:
time_spent_unproductively = time_i_sleep.add(time_i_watch_netflix)
time_spent_productively = time_i_work.add(time_i_do_chores)
time_spent_unproductively.greater_than(time_spent_productively)
更好的 TimePeriod 类
但最好的解决方案是实现 Python 的魔法方法,并将我们的逻辑移到其中。在这里,我们将实现与“+”和“>”运算符对应的__add__
and__gt__
方法。现在就来试试吧:
class TimePeriod:
def __init__(self, hours=0, minutes=0):
self.hours = hours
self.minutes = minutes
def __add__(self, other):
minutes = self.minutes + other.minutes
hours = self.hours + other.hours
if minutes >= 60:
minutes -= 60
hours += 1
return TimePeriod(hours, minutes)
def __gt__(self, other):
if self.hours > other.hours:
return True
elif self.hours < other.hours:
return False
elif self.minutes > other.minutes:
return True
else:
return False
现在,我们可以将复杂的操作重写为:
(time_i_sleep + time_i_watch_netflix) > (time_i_work + time_i_do_chores)
好了!代码更加清晰易读,因为我们现在可以使用像“+”和“>”这样易于识别的符号了。现在,任何阅读我们代码的人都能一眼看出它到底做了什么。
添加更多功能
如果我们想比较两个 TimePeriod 对象,并使用 '==' 运算符检查它们是否相等,该怎么办?我们可以简单地实现eq方法,如下所示:
def __eq__(self, other):
return self.hours == other.hours and self.minutes == other.minutes
Python 的魔法方法并不仅限于算术和比较运算。其中一个最有用的方法,也是你可能经常遇到的,就是__str__
,它允许你创建类的易于阅读的字符串表示。
def __str__(self):
return f"{self.hours} hours, {self.minutes} minutes"
您可以通过调用来获取此字符串表示形式str(time_period)
,这在调试和日志记录中很有用。
如果我们愿意,我们甚至可以将我们的课程变成某种字典!
def __getitem__(self, item):
if item == 'hours':
return self.hours
elif item == 'minutes':
return self.minutes
else:
raise KeyError()
这将允许我们使用字典访问语法 [] 来访问时间段内的小时数(使用 ['hours']),以及分钟数(使用 ['minutes'])。在所有其他情况下,它会引发 KeyError,指示键不存在。
其他有用的魔法方法
Python 的 dunder 方法完整列表可在Python 语言参考中找到。这里,我列出了一些最有趣的方法。欢迎在评论区提出补充建议。
__new__
:虽然该__init__
方法(我们已经很熟悉了)在初始化类实例时被调用,但该__new__
方法的调用时间更早,在实际创建实例时就被调用了。您可以在这里阅读更多相关信息。__call__
:该__call__
方法允许我们的类实例可调用,就像方法或函数一样!此类可调用类在 Django 中被广泛使用,例如在中间件中。__len__
:这允许您定义 Python 内置len()
函数的实现。__repr__
:这与魔术方法类似__str__
,因为它允许您定义类的字符串表示形式。然而,区别在于,__str__
前者面向最终用户,提供更用户友好、更非正式的字符串;__repr__
后者面向开发人员,可能包含有关类内部状态的更复杂信息。您可以在此处阅读更多关于此区别的信息。__setitem__
:我们已经看过这个__getitem__
方法了。__setitem__
是同一枚硬币的另一面——前者允许获取与键对应的值,而后者允许设置值。__enter__
&__exit__
:感谢 Waylon Walker 对此的评论。这两个方法一起使用时,可将您的类用作上下文管理器,这是一种确保重要代码(尤其是在处理文件等外部资源时)始终执行的有效方法。
综上所述
我们已经看到,只需对代码进行一些细微的调整,就能显著提升代码的清晰度和可读性,并让我们能够使用强大的 Python 语言特性。当然,本文只是粗略地介绍了魔法方法所能提供的功能,因此我建议您浏览以下链接,探索它们有趣的新用例和可能性。
如果您发现本文中有任何错误或想提出任何类型的改进建议,请随时在下面发表评论。
该图片由Gerald Friedrich在Pixabay上发布
参考文献和进一步阅读
Python 魔法方法讲解
Python 魔法方法指南
Python 魔法方法示例