如何编写出优秀的 Python Class

如何编写出优秀的 Python Class

Python 是大多数数据科学和机器学习项目的首选语言。然而,并非每个数据科学家都拥有丰富的 Python 经验,都能了解并熟练应用该语言的各项功能。这是可以理解的,但同时也很可惜。为什么?

因为了解一门语言的细节可以帮助程序员编写重复性更低、可读性更强、且更容易维护的代码。总的来说,如果你充分利用一门语言,代码质量会更高,更重要的是,你的工作过程中会充满更多乐趣。

因此,本文的目的在于帮助你提升 Python 知识水平,编写更多的优秀代码,这可能会给你的伙伴或同事留下深刻印象,并从中获得更多乐趣!具体来说,本文主要会讨论dunder-、special- 或 magic 方法。是不是非常好奇本文的内容?那就开始吧。如果你想了解更多数据分析相关内容,可以阅读以下这些文章:
Python机器学习库:pycarets新增时间序列模块
DS vs DE:数据科学家与数据工程师的薪资对比
Pandas和SQL,数据科学家应该用哪个?
如何准备DS数据科学家面试?

魔法方法(Magic Methods)

正如标题所说,我们将讨论 Python 魔术方法。你可能阅读过有关 dunder 或special方法的文章,他们本身都指的是相同的东西。本文,我将用“魔术方法”这个词。那么魔法方法到底是什么呢?

基础知识

魔术方法是属于Class的函数,既可以是实例,也可以是Class方法,它们非常容易识别,因为这类方法的开头和结尾都有双下划线,比如 __actual_name__。这也是 dunder术语的来源—double underscores(双下划线)。我花了很久才发现。

重点是,这并不意味着你可以直接调用这些魔法方法!当然,你可以编写 YourClass().__actual_name__() ,但我不建议你这样做!

那么,如何调用魔术方法呢?你需要从应用于Class或Class实例的某些操作中调用这类方法。例如,调用 str(YourClass()) ,激活魔术方法 __str__ ;或执行YourClass() + YourClass() ,从而激活 __add__ 。

那魔法方法的好处有哪些呢?魔法方法可以帮你编写可以和 python 内置方法同时使用的Class。这样,你可以编写出更易读、更简洁的代码。我在上边已详细说明过这一点了。

为了强调魔术方法优势,并了解在进行机器学习或数据科学时带来的好处,让我们可以通过具体的例子分析。

示例:自定义日期时间范围

在本例中,我想展示如何通过魔术方法编写类似于内置 range 函数的内容。与内置版本相比,本示例包含的功能更多,更重要的是,我们创建的是日期时间范围,而非数字范围。当然,你也可以使用 Pandas 或其他库,但我认为,本示例有助于我们理解魔术方法背后的概念,同时展示在处理数据时所使用的魔术方法。

仅仅讨论或写代码,但却看不到真实且有效的代码,会让你的学习过程变得枯燥无味。所以我们先暂停我们的讨论,看看在操作过程中如何实现自定义日期时间范围。

from datetime import datetime, timedelta
from typing import Iterable
from math import ceil


class DateTimeRange:
    def __init__(self, start: datetime, end_:datetime, step:timedelta = timedelta(seconds=1)):
        self._start = start
        self._end = end_
        self._step = step

    def __iter__(self) -> Iterable[datetime]:
        point = self._start
        while point < self._end:
            yield point
            point += self._step

    def __len__(self) -> int:
        return ceil((self._end - self._start) / self._step)

    def __contains__(self, item: datetime) -> bool:
        mod = divmod(item - self._start, self._step)
        return item >= self._start and item < self._end and mod[1] == timedelta(0)

    def __getitem__(self, item: int) -> datetime:
        n_steps = item if item >= 0 else len(self) + item
        return_value = self._start + n_steps * self._step
        if return_value not in self:
            raise IndexError()

        return return_value
   
    def __str__(self):
        return f"Datetime Range [{self._start}, {self._end}) with step {self._step}"

# Usage
my_range = DateTimeRange(datetime(2021,1,1), datetime(2021,12,1), timedelta(days=12))
print(my_range)
assert len(my_range) == len(list(my_range))
my_range[-2] in my_range
my_range[2] + timedelta(seconds=12) in my_range
for r in my_range:
    do_something(r)

我们可以看到,代码数量非常多。如果你感觉操作难度很大,打算放弃,请耐心听我讲解。总的来说,我在上方总共使用了六种魔法方法,具体内容如下:

第一个,可能也是许多人都知道的方法— __init__ 方法。我们都知道,该方法主要用于初始化Class实例属性。这里,我们设置了范围Class的开始(Start)和结束(End),以及步长(Step Size)。这与我们在创建内置范围函数时的操作类似。

下一个是 __iter__ 方法。这个方法很重要,可以生成我们日期时间范围内的所有元素。这个函数是一个生成器函数,每创建一个元素,都将其交给调用者,并让调用者处理这些元素,直至范围结束。在查看 yield 关键字时,你可以轻松识别生成器函数。此语句暂停函数,保存其所有状态,然后在后续调用中继续调用。这样,我们无需将每个元素都放在内存中,但还是可以每次使用一个元素。

当每个项目都占用大量内存,或者当你拥有大量项目时,把所有内容都放在内存中就非常方便了。例如,你可以执行 list(DateTimeRange(datetime(1900,1,1), datetime(2000,1,1)),这样会创建一个包含 3184617600 个日期时间条目的列表。而如果使用生成器,你就可以非常轻松地逐个处理这些元素。

现在,你可以看到,所出的结果不是列表或组。然而,为了处理这个类似于列表或组的 DateTimeRange Class,我增加了另外三个魔法方法,即 __len__ 、 __contains__ 和 __getitem__ 。

通过 __len__ ,你可以调用 len(my_range) ,找出属于你的范围的元素数量。例如,当你迭代所有元素,并想知道你已经处理了多少元素时,这个方法会非常有用。就像是在告诉你,嘿,我要处理大量数据,你可以去喝杯咖啡了。

通过 __contains__,你可以用 my_range 中的内置语法element,检查某个元素是否属于你的范围。这样操作的好处在于,我们是通过纯数学方法,无需比较给定元素与范围内的所有元素。这意味着,检查一个元素是否在你的范围内是一个常量时间操作,不依赖于实际范围实例的大小。同样,对于我们在处理数据时经常遇到的大范围情况,也会变得方便许多。

通过 __getitem__ ,你可以使用索引语法在对象中检索条目。例如,你可以通过 my_range[-1], 获取范围的最后一个元素。我必须承认,在本示例中,这个方法可能是用处最小的那个。但是,一般来说,使用 __getitem__ 可以帮助你编写干净且可读的界面。

第六个也是最后一个魔术方法是 __str__ 。此方法的作用是帮助你将Class的实例转换为字符串。在调用 print(my_range) 时,使用此方法会让你的操作变得非常方便,因为 print 必须将实例转换为字符串,因此我推荐使用 __str__ 方法。

图源:Unsplash 摄影:Dollar Gill

总结

本文介绍了 Python 中魔术方法背后的基本概念。通过实例,我向你展示了如何使用其中的一些方法,在我看来,这些方法在处理数据时非常方便。当然,还有许多其他的方法,例如创建上下文管理器,或增强class,这里我就不一一赘述了。我建议你继续探索这个领域,不断学习!

感谢你的关注。如有任何疑问、意见或建议,请随时在文章下方留言。你还可以订阅我们的YouTube频道,观看大量数据科学相关公开课:https://www.youtube.com/channel/UCa8NLpvi70mHVsW4J_x9OeQ;在LinkedIn上关注我们,扩展你的人际网络!https://www.linkedin.com/company/dataapplab/

原文作者:Simon Hawe
翻译作者:Lia
美工编辑:过儿
校对审稿:Jiawei Tong
原文链接:https://towardsdatascience.com/how-to-write-awesome-python-classes-f2e1f05e51a9