摘要:在 Python 中,具有前导和尾随双下划线的方法名称保留用于特殊用途,例如对象构造函数的__init__方法,或使对象可调用的__call__方法。这些方法称为dunder方法。dunder这里的意思是“双下划线”。这些dunder方法通常被称为魔法方法——
在 Python 中,具有前导和尾随双下划线的方法名称保留用于特殊用途,例如对象构造函数的__init__方法,或使对象可调用的__call__方法。这些方法称为dunder方法。 dunder这里的意思是“双下划线”。这些dunder方法通常被称为魔法方法——尽管它们并没有什么神奇之处。
选择在两侧用双下划线包装这些函数实际上只是保持语言简单的一种方法。 dunders实现了使某些方法变得特殊的预期目标,同时使它们在除命名约定之外的各个方面与其他普通方法相同。
Dunder方法可用于模拟用户定义对象的内置类型的行为。考虑以下示例,其中向自己的对象添加len方法支持。
class NoLendefined: pass>>> obj = NoLenDefined>>> len(obj)TypeError: "object of type 'NoLenDefined' has no len"
添加__len__ dunder 方法将修复该错误。
class LenDefined: def __len__: return 1>>> obj = LenDefined>>> len(obj)1
注意:len内部调用特殊方法__len__返回对象的长度。
提示:可以在对象上使用dir方法来查看该类继承的dunder方法。示例: dir(int)。
创建对象时,通过调用该对象的__init__方法对其进行初始化。
class Person: def __init__(self, name, age): self.name = name self.age = age>>> person = Person('Sarah', 25)>>> person
当调用__init__方法时,对象(在本例中为 person)作为“self”传递。方法调用中使用的其他参数将作为函数的其余参数传递。
当定义一个自定义类并尝试将其实例打印到控制台时,结果并不能很好地描述该对象,因为默认的“到字符串”转换是基本的并且缺乏细节。让我们考虑以下示例:
class Person: def __init__(self, name, age): self.name = name self.age = age>>> person = Person('Sarah', 25)>>> print(person)>>> person
默认情况下,我们获取类的名称和对象的id 。更理想的是打印对象的属性,如下所示:
print (person.name, person.age)Sarah 25
为了实现这一点,我们可以添加自己的to_string方法,但这样做会忽略 python 将对象表示为字符串的内置机制。因此,让我们向类中添加“dunder”方法来根据需要描述我们的对象。
class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return "Person: {}, Age: {}".format(self.name, self.age)>>> person = Person('Sarah', 25)>>> print(person)Person: Sarah, Age: 25>>> person
因此,可以重写__str__方法以返回任何用户定义类的可打印字符串表示形式。
>>> print(person)Person: Sarah, Age: 25>>> str(person)Person: Sarah, Age: 25>>> '{}'.format(person)Person: Sarah, Age: 25
__repr__与__str__类似,但使用情况不同。如果我们在解释器会话中检查person对象,我们仍然得到
输出。让我们重新定义我们的类以包含两个dunder方法。
class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): print('inside str') return "Person: {}, Age: {}".format(self.name, self.age) def __repr__(self): print('inside repr') return " Person: {}, Age: {}".format(self.name, self.age)>>> person = Person('Sarah', 25)>>> personinside reprPerson: Sarah, Age: 25>>>print(person)inside strPerson: Sarah, Age: 25
如上所示,当在解释器会话中检查对象时,会调用__repr__ 。
在较高级别上, __str__用于为最终用户创建输出,而__repr__主要用于调试和开发。 repr 的目标是明确,str 的目标是可读。
我们可以使用__str__和__repr__ “dunder” 方法在我们自己的类中控制字符串转换
__repr__计算对象的“官方”字符串表示(具有有关该对象的所有信息),而__str__用于对象的“非正式”字符串表示。
如果我们不添加__str__方法,Python 在搜索__str__时将依靠__repr__的结果。因此,建议将__repr__添加到我们的类中。
Python 内置类型list 、 str和bytes可以使用切片运算符来访问元素范围。在类中实现__getitem__ 、 __setitem__允许其实例使用 (索引器)运算符。因此, __getitem__和__setitem__ dunder 方法用于列表索引、字典查找或访问值范围。为了更好地理解这个概念,让我们考虑一下创建自己的自定义列表的示例。
import random as ranclass CustomList: def __init__(self, num): self.my_list = [ran.randrange(1,101,1) for _ in range(num)]>>> obj = CustomList(5)>>> obj.my_list[59, 83, 96, 86, 59]>>> len(obj)AttributeError:>>> for no in obj:... print (no)AttributeError:>>> obj[1]AttributeError:
使用上面的类定义,我们无法迭代我们的对象,因为上面的语句引发了AttributeError 。让我们在类中实现 dunder 方法以使其可迭代。
import random as ranclass CustomList: def __init__(self, num): self.my_list = [ran.randrange(1,101,1) for _ in range(num)] def __str__(self): return str(self.my_list) def __setitem__(self, index, value): self.my_list[index] = value def __getitem__(self, index): return self.my_list[index] def __len__(self): return len(self.my_list)>>> obj = CustomList(5)>>> print(obj)[59, 83, 96, 86, 59]>>> len(obj)5>>> obj[1]83>>> for item in obj:... print (item)5983968659
因此,使用__setitem__ 、 __getitem__ 、 __len__ dunder 方法允许我们使用切片运算符并使我们的对象可迭代。
注意:__iter__和__next__dunder 方法也用于编写可迭代对象,但它们超出了此处讨论的范围,我们将在单独的帖子中讨论它们。
通过添加__call__ dunder 方法,我们可以使任何对象像常规函数一样可调用。让我们考虑下面的玩具(不是很有意义)示例,只是为了展示__call__方法。
class Person: def __init__(self, name, age): self.name = name self.age = age def __call__(self): print ('Person: {}, Age: {}'.format(self.name, self.age))>>> person = Person>>> personPerson: Sarah, Age: 25
__call__在具有需要经常更改状态的实例的类中特别有用。 “调用”实例可以是更改对象状态的直观且优雅的方式。一个示例可能是表示实体在平面上的位置的类:
class Entity: '''Callable to update the entity's position.''' def __init__(self, size, x, y): self.x, self.y = x, y self.size = size def __call__(self, x, y): '''Change the position of the entity.''' self.x, self.y = x, y>>> point = Entity(10, 20)>>> print(point.x, point.y)10 20>>> point(30, 40)>>> print (point.x, point.y)30 40
一般来说,每当我们想要提供一个类似于简单函数的简单接口时,就会使用__call__ 。然而,很难看透这一点。
来源:自由坦荡的湖泊AI
免责声明:本站系转载,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与本站联系,我们将在第一时间删除内容!