Python中的特殊方法

108 阅读3分钟

什么是特殊方法

Python提供了一些特殊方法,有了这些特殊方法,你可以自定义类/对象。

使用特殊方法自定义类有2个好处:

  1. 利用Python语言的一致性。如果有人使用你的自定义类,不需要特别记方法名。
  2. 可以更加方便的使用Python标准库。

特殊方法的分类

迭代

  • __iter__: 当python解释器遇到for i in x这个语句时,背后调用的是__iter__()
  • __reversed__
  • __next__
  • __missing__:当__getitem__碰到找不到键的时候,Python就会自动调用它。

集合类

  • __len__:在执行len(my_object)时,Python解释器背后调用的是__len__()
  • __getitem__:在执行my_object[i]时,Python解释器背后调用的是__getitem()
  • __setitem__
  • __delitem__
  • __contains__

属性访问

  • __getattr__
  • __getattribute__
  • __setattr__
  • __delattr__
  • __dir__
  • __get__
  • __set__
  • __delete__

运算符重载

  • __abs__
  • __bool__ :bool(x)背后是x.bool()的结果。如果不存在__bool__方法,那么bool(x)会尝试调用x.len(),如果返回0,则bool会返回false;否则会返回True。
  • __complex__
  • __int__
  • __float__
  • __hash__
  • __index__

函数和方法的调用

  • __call__

对象的创建和销毁

  • __new__
  • __init__
  • __del__
  • __prepare__
  • __instancecheck__
  • __subclasscheck__

字符串表示形式和格式化

  • __repr__:以便于开发者理解的方式返回对象的字符串表示形式。%r 可用来获取对象的字符串表示形式。例如,在requests库中,Response类的__repr__方法如下:
def __repr__(self):
    # <Response [200]>
    return f"<Response [{self.status_code}]>"
  • __str__:以便于用户理解的方式返回对象的字符串表示形式。在str()函数时被调用,或者是在print函数打印一个对象的时候才被调用。如果一个对象没有__str__函数,而Python又需要调用它的时候,解释器会用__repr__作为替代。例如,在requests库中,Response类的__str__方法如下:
def __str__(self):
    return self.content.decode(self.encoding)
  • __format__:会被内置的format()函数和str.format()方法调用,使用特殊的格式代码显示对象的字符串表示形式。
  • __bytes__:返回对象的字节序列表示形式。

管理上下文

  • __enter__
  • __exit__

使用特殊方法实现一个自定义序列

import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])

# 实现一个扑克牌类
class FrenchDeck:
    # 扑克牌取值
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    # 扑克牌花色
    suits = 'spades diamonds clubs hearts'.split()
    
    def __init__(self):
        # 排列组合生成一套扑克牌, 用列表来存储
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]
   def __len__(self):
       # 通过获取列表的长度,从而实现FrenchDeck的长度获取
       # 将具体实现代理给列表
       return len(self._cards)
       
   def __getitem__(self):
       # 通过获取列表的元素,从而实现FrenchDeck特定元素的获取
       # 将具体实现代理给列表
       return self._cards[position]

使用特殊方法实现一个向量类

from math import hypot
class Vector:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    
    def __repr__(self):
        return 'Vector(%r, %r)' % (self.x, self.y)
    
    def __abs__(self):
        return hypot(self.x, self.y)
    
    def __bool__(self):
        return bool(abs(self))
    
    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

映射的弹性键查询

有的时候为了方便起见,就算某个键在映射里不存在,我们也希望在通过这个键读取值得时候能得到一个默认值。

# 在查询的时候,把非字符串得键转换为字符串

# 继承dict
class StrKeyDict0(dict):
    # missing方法的职责:如果key在字典不存在,而key又是一个数值类型,那么可以试图将转换成str,再次查找是否存在
    # 如果key在字典中不存在,而key又是一个str,那么在missing中即使转换成str,也没有发生任何变化,key还是找不到
    def __missing__(self, key):
        # 为什么要做类型判断:
        # 如果key的类型是str,但是又调用到了__missing__方法,说明该key在字典中确实不存在
        # 如果不做类型判断,那么又会调用self[str(key)],str(key)不存在,又会调用__missing__,那么就会陷入无限递归
        if isinstance(key, str):
            raise KeyError(key)
        # str(key)在字典中存在,返回value
        return self[str(key)]
    
    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default
    
    # 为什么要有contains
    # 因为在 for i in x,有可能i可能是数值,那么为了保证数值键也可以被查询到,所以实现了__contains__
    def __contains__(self, key):
        # 这里没有用 key in self 而用了 self.keys的原因是
        # 如果是 for key in self,那么会调用自身的__contains__方法,就会陷入递归调用
        return key in self.keys() or str(key) in self.keys()