仅用于站内搜索,没有排版格式,具体信息请跳转上方微信公众号内链接
你可能不知道,Python里那些用双下划线包裹的\“魔法方法\“(Dunder方法),其实是提升代码质量的绝佳工具。但有趣的是,很多经验丰富的开发者对这些方法也只是一知半解。
先说句公道话:这其实情有可原。因为在多数情况下,Dunder方法的作用是\“锦上添花\“——它们能让代码更简洁规范,但不用它们也能完成任务。有时候我们甚至不知不觉就在使用这些特殊方法了。
如果你符合以下任一情况:
经常用Python但不太了解这个特性
像我一样痴迷编程语言的精妙设计
想让代码既专业又优雅
那么,这篇文章就是为你准备的!我们将探索如何巧妙运用这些\“魔法方法\“来:
大幅简化代码逻辑
提升代码可读性
写出更Pythonic的优雅代码
如果说我在生活中学到了什么,那就是并非所有东西都像第一眼看上去那样,Python也不例外。
看一个看似简单的例子:
这是我们可以在Python中定义的最“空”的自定义类,因为我们没有定义属性或方法。它是如此的空,你会认为你什么也做不了。
然而,事实并非如此。例如,如果您尝试创建该类的实例,甚至比较两个实例是否相等,Python都不会抱怨:
当然,这并不是魔法。简单地说,利用标准的object接口,Python中的任何对象都继承了一些默认属性和方法,这些属性和方法可以让用户与之进行最少的交互。
虽然这些方法看起来是隐藏的,但它们并不是不可见的。要访问可用的方法,包括Python自己分配的方法,只需使用dir()内置函数。对于我们的空类,我们得到
正是这些方法可以解释我们之前观察到的行为。例如,由于该类实际上有一个__init__方法,我们就不应该对我们可以实例化一个该类的对象感到惊讶。
最后输出中显示的所有方法都属于一个特殊的群体–猜猜看–dunder方法。dunder是双下划线(doubleunderscore)的缩写,指的是这些方法名称开头和结尾的双下划线。
它们之所以特殊,有以下几个原因:
它们内置于每个对象中:每个Python对象都配备了由其类型决定的一组特定的dunder方法。
它们是隐式调用的:许多dunder方法是通过与Python本机运算符或内置函数的交互自动触发的。例如,用==比较两个对象相当于调用它们的__eq__方法。
它们是可定制的:您可以覆盖现有的dunder方法,或者为您的类定义新的方法,以便在保留隐式调用的同时赋予它们自定义的行为。
尽管是最常用的方法,init__也是最专业的dunder方法之一。它没有充分展示dunder方法的灵活性和强大功能,而这些方法可以让您重新定义对象与原生Python特性的交互方式。
定义一个类来表示商店中出售的物品,并通过指定名称和价格来创建一个实例。
如果我们尝试显示item变量的内容,会发生什么?现在,Python所能做的就是告诉我们它是什么类型的对象,以及它在内存中的分配位置:
试着得到一个信息量更大、更漂亮的输出!
要做到这一点,我们可以覆盖__repr__dunder,当在交互式Python控制台中键入一个类实例时,它的输出将完全是打印出来的,而且–只要没有覆盖另一个dunder方法__str–当试图调用print()时也是如此。
注意:通常的做法是让__repr__提供重新创建打印实例所需的语法。因此,在后一种情况下,我们希望输出Item(name=\“Milk(1L)\“,price=0. 99)。
定义对象与Python本地运算符之间的交互
假设我们想创建一个新类,即Grocery,它允许我们建立一个Item及其数量的集合。
在这种情况下,我们可以使用dunder方法来进行一些标准操作,例如
使用+运算符将特定数量的Item添加到Grocery中
使用for循环直接遍历Grocery类
使用括号[]符号从Grocery类中访问特定的Item
为了实现这一目标,我们将定义(我们已经看到泛型类默认情况下没有这些方法)dunder方法__add__,__iter__和__getitem__。
初始化一个Grocery实例,并打印其主要属性items. 的内容。
然后,我们使用+运算符添加一个新项目,并验证更改是否已生效。
既友好又明确,对吗?
通过__iter__方法,我们可以按照该方法中实现的逻辑对一个Grocery对象进行循环(即,隐式循环将遍历可遍历属性items中包含的元素)。
同样,访问元素也是通过定义__getitem__函数来处理的:
从本质上讲,我们为Grocery类分配了一些类似字典的标准行为,同时也允许进行一些该数据类型本机无法进行的操作。
增强功能:使类可调用,以实现简单性和强大功能。
最后,让我们用一个示例来结束对dunder方法的深入探讨,展示它们如何成为我们的强大工具。
想象一下,我们实现了一个函数,它可以根据特定输入执行确定性的慢速计算。为了简单起见,我们将以一个内置time. sleep为几秒的标识函数为例。
如果我们对同一输入运行两次函数,会发生什么情况?那么,现在计算将被执行两次,这意味着我们将两次获得相同的输出,在整个执行时间内等待两次(即总共10秒)。
这合理吗?为什么我们要对相同的输入进行相同的计算(导致相同的输出),尤其是在计算过程很慢的情况下?
一种可能的解决方案是将该函数的执行“封装”在类的__call__dunder方法中。
这使得类的实例可以像函数一样被调用–这意味着我们可以使用简单的语法my_class_instance(*args,**kwargs)–同时也允许我们使用属性作为缓存来减少计算时间。
通过这种方法,我们还可以灵活地创建多个进程(即类实例),每个进程都有自己的本地缓存。
不出所料,函数在第一次运行后会被缓存起来,这样就不需要进行第二次计算,从而将总时间缩短了一半。
如上所述,如果需要,我们甚至可以创建该类的独立实例,每个实例都有自己的缓存。
dunder方法是一个简单而强大的优化技巧,它不仅可以减少冗余计算,还可以通过本地特定实例缓存提供灵活性。
Dunder方法(就是那些用双下划线__包裹的特殊方法)在Python中是个很大的话题,而且还在不断丰富。这篇文章当然没法面面俱到地讲完所有内容。
我写这些主要是想帮你弄明白两件事:
Dunder方法到底是什么?
怎么用它们解决实际编程中常见的问题?
说实话,不是每个程序员都必须掌握这些方法。但就我个人经验来说,当我真正搞懂它们之后,写代码的效率提高了很多。相信对你也会很有帮助。
使用Dunder方法最大的好处就是:
不用重复造轮子
让代码更简洁易读
更符合Python的编程风格
这些优点,对你一定是有用的。对吧?点个赞吧❤️支持一下
长按👇关注-数据STUDIO-设为星标,干货速递