Python魔法函数详解:__repr__、__str__与序列操作实现
本文深入解析Python中常用的魔法函数,包括用于对象字符串表示的__repr__与__str__的区别,以及实现序列行为的__getitem__、__len__、__setitem__和__delitem__。通过具体代码示例,展示如何自定义类的行为以支持索引访问、长度查询和属性赋值,帮助开发者更好地理解Python的面向对象特性。
Python魔法函数概述
Python提供了一系列以双下划线开头和结尾的特殊方法,通常被称为魔法函数或魔术方法。这些方法允许开发者自定义类的行为,使其在特定操作下表现出预期的结果。
__repr__与对象字符串表示
__repr__
在Python中,__repr__函数对应内置的repr(object)函数,其目的是返回一个对象的“官方”字符串表示形式。这个字符串通常旨在准确反映对象的内部状态,便于调试和开发。如果直接打印一个未定义该方法的类实例,结果往往不够直观。
class A():
def __init__(self,name=None,id=1):
self.id=id
self.name=name
if __name__ == '__main__':
a=A()
print(a)
输出结果
<__main__.A object at 0x0000018DF8E7EAC8>
默认情况下,直接打印类实例会返回对象的内存地址,格式如<__main__.ClassName object at 0x...>,这对用户来说并不友好。为了改善这一点,我们可以通过定义__repr__方法来自定义输出内容,再次运行代码观察变化。
class A():
def __init__(self,name=None,id=1):
self.id=id
self.name=name
def __repr__(self):
return "进入函数"
if __name__ == '__main__':
print(A())
输出结果
进入函数
__str__
__str__与__repr__的区别
class A():
def __init__(self,name=None,id=1):
self.id=id
self.name=name
def __str__(self):
return "进入函数"
if __name__ == '__main__':
print(A())
输出结果
进入函数
比较repr和str
虽然在使用print()函数时,__repr__和__str__的表现可能看起来相同,但二者有本质区别。__str__主要面向最终用户,旨在提供易读的、非正式的字符串表示;而__repr__面向开发者,旨在提供精确的、可用于重建对象的字符串表示。在交互式命令行中,直接输入变量名会调用__repr__,而__str__仅在明确调用str()或print()时生效。此外,如果未定义__str__,Python会回退使用__repr__。
__getitem__实现索引访问
__getitem__
如果在类中定义了__getitem__(self, key)方法,其实例对象A就可以通过A[key]的方式进行取值。当执行A[key]运算时,Python会自动调用类中的__getitem__方法,并将key作为参数传递进去。
class A():
def __init__(self,name=None,id=1):
self.id=id
self.name=name
def __repr__(self):
return "进入函数"
def __getitem__(self, item):
return item
if __name__ == '__main__':
a=A('lisa','123')
print(a['name'])
print(a[124])
输出
name
124
无论实例对象的key是否存在,调用A[key]时都会触发__getitem__方法,其返回值即为该方法中指定的return值。如果__getitem__实现不当,可能导致逻辑错误或异常。我们可以修改代码,调整__getitem__的返回值逻辑,以更好地处理属性访问。
class A():
def __init__(self,name=None,id=1):
self.id=id
self.name=name
def __repr__(self):
return "进入函数"
def __getitem__(self, item):
return self.__dict__[item]
if __name__ == '__main__':
a=A('lisa','123')
print(a['name'])
print(a[123])
输出
lisa
keyerror:123
上述修改后的代码输出了'lisa'和一个KeyError异常。具体而言,self.__dict__获取了当前实例所有属性的字典,通过[item]键取值。传入'name'时,成功获取了对应的属性值'lisa';而传入'123'时,由于该属性不存在,触发了KeyError。这是字典内部实现机制的一部分,也是__getitem__常见的应用场景。
再来看一个例子,代码里已经加入了注释:
__len__获取对象长度
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
# 具名元组动态创建一个类Card,并含有两个属性rank和suit
# 用以构建只有少数属性但是没有方法的对象
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA') # 扑克牌2到A组成的列表
suits = 'spades diamonds clubs hearts'.split() # 四种花色
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] # 笛卡尔积,13*4=52(除去两个王)
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
# 调用f[0]时会进入
return self._cards[position]
if __name__ == '__main__':
f = FrenchDeck()
print(f[0])
# 在这里f[0]实际是f.__getitem__(0)
输出
Card(rank='2', suit='spades')
在这个例子中,我们还看到了__len__方法的使用。那么,__len__方法的作用是什么呢?我们继续往下看。
__len__
在上面的例子中,我们使用了__len__方法。该方法在何时被调用呢?通过一个小例子来说明。
class B():
class B():
def __init__(self):
self.a_list = range(10)
def __len__(self):
return len(self.a_list)
if __name__ == '__main__':
b = B()
print(len(b))
#在这里等价于
#print(b.__len__())
输出
10
当调用内置的len()函数时,Python会自动调用对象的__len__方法。
__setitem__实现属性赋值
__setitem__
__setitem__(self, key, value)方法用于按特定方式存储与key相关的value。在设置类实例属性时,如果通过类似字典的方式赋值,会自动调用此方法。
class B():
class B():
def __init__(self):
self.a_list = range(10)
def __setitem__(self, key, value):
self.__dict__[key] = value
def cfun(a, b, c):
print("新加入函数c")
if __name__ == '__main__':
b = B()
b['a_list'] = "123" # 这个会调用B类的\__setitem_方法_
B.__setitem__ = cfun # 改变settime方式变为cfun这个函数
b['a_list'] = "123" # 这次实际会调用cfun函数
print(b.a_list)
输出
新加入函数c
123
__delitem__与__eq__方法
__delitem__
执行del函数时会调用__delitem__。如果类继承了abc.MutableSequence,则必须实现__delitem__方法,因为这是MutableSequence类定义的一个抽象方法,用于支持删除操作。
__eq__
表达式a == b等同于调用a.__eq__(b)。你可以在自己的类中定义__eq__方法,以自定义实例之间的相等比较逻辑。如果不覆盖__eq__,则使用从object继承的默认行为,即比较内存地址。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









