Python容器类魔法方法详解与自定义实现

Oliver
Oliver
发布时间:2026-10-12| 2人浏览过
原创

本文详细解析Python中实现容器行为所需的魔法方法,包括__len__、__iter__、__contains__、__getitem__等。通过自定义列表和字典类的代码示例,演示如何限制元素类型、索引范围及实现缺失键处理,帮助开发者深入理解Python对象模型与协议机制。

本文深入解析Python中的容器类魔法方法,通过具体代码示例展示其工作原理,为开发者在自定义数据结构时提供实用的参考指南。

为了巩固理解并便于日后查阅,特此整理记录相关知识点。

序列(如集合、列表、字符串)和映射(如字典)本质上都是元素的集合。要实现它们的基本行为协议,不可变对象需实现两个核心协议,而可变对象则需实现四个。

  • __len__(self):返回元素数量,这是实现不可变对象协议的必要条件,对应内置函数 len。
  • __iter__:返回一个迭代器对象,该对象需具备 __next__ 方法,从而支持 for 循环遍历。
  • __contains__:定义成员检测行为,执行 xx.__contains__(22) 的效果等同于判断 22 in xx。
  • __getitem__(self, key) 或 __getitem__(self, index):根据传入的键或索引返回关联的值,这是实现不可变对象协议的必要条件。
  • __setitem__(self, key, values) 或 __setitem__(self, index, values):用于设置指定键或索引对应的值。
  • __delitem__(self, key):用于删除指定键对应的值。
  • __missing__:此方法类似于 __getattr__,当访问不存在的键时触发。此前用列表测试时产生混淆,实际上该方法主要对字典类型有效。
  • __del__:析构函数,在类实例对象被销毁时执行清理操作。

接下来,我编写一个自定义的类似列表的类。实例化后,该类默认包含10个 None 参数,且前5个 None 元素不可删除(此为初步构思,开始编码)。

def check_index(index):
  if index < 5:
    raise IndexError('index must greater than 10')
 
 
class S_List:
  def __init__(self):
    self.ll = [None] * 10
 
  def __len__(self): # 提取参数长度
    return len(self.ll)
 
  def __getitem__(self, index): # 取出参数
    return self.ll[index]
 
  def __setitem__(self, index, value): # 设置参数
    check_index(index)
    self.ll[index] = value
 
  def __delitem__(self, index):
    check_index(index)
    self.ll.pop(index)
 
  def __str__(self): # 打印对象时,输出列表本身
    return str(self.ll)
 
  def __del__(self): # 没有手工删除在程序结束时释放
    print('我被释放了!')
 
sl = S_List()
del sl[3]
 
print(isinstance(sl, S_List))
print(f'输出原始数据:{sl}')
sl[6] = 'six'
print(f'修改后原始数据:{sl}')
print(f'随便取一个值:{sl[1]}')
del sl[6]
print(f'第二次修改后原始数据:{sl}')
del sl[3]
# sl[4] = 'oh'
print(sl)

正常执行输出如下:

True
输出原始数据:[None, None, None, None, None, None, None, None, None, None]
修改后原始数据:[None, None, None, None, None, None, 'six', None, None, None]
随便取一个值:None
第二次修改后原始数据:[None, None, None, None, None, None, None, None, None]
[None, None, None, None, None, None, None, None, None]
我被释放了!

触发错误时的提示如下:

Traceback (most recent call last):
 File "/Users/shijianzhong/Desktop/yunzuan_buy/study_base.py", line 81, in <module>
  del sl[3]
 File "/Users/shijianzhong/Desktop/yunzuan_buy/study_base.py", line 73, in __delitem__
  check_index(index)
 File "/Users/shijianzhong/Desktop/yunzuan_buy/study_base.py", line 53, in check_index
  raise IndexError('index must greater than 10')
IndexError: index must greater than 10
我被释放了!

这是一个自定义的、几乎不包含额外方法的伪字典类。它不支持增加元素,且由于未实现 index 和 count 等方法,这些功能无法使用。

更优的做法是继承 list 或 dict 类,并在子类中修改所需的条件限制。这样实例化的对象可以继承父类的全部方法。

另一种方式是无需初始化自定义变量,直接利用 __dict__ 来实现类似字典的取值与赋值操作。

class Ii:
  def __getitem__(self, item):
    return self.__dict__[item]
  def __setitem__(self, key, value):
    self.__dict__[key] = value
 
li = Ii()
li[3] = 5
print(li[3])
 
# 5

现在,我可以真正定义一个“超级列表”,根据特定需求进行定制。要求该列表初始化时包含5个 None 元素,前5个元素禁止修改,后续添加的元素必须为字符串类型。

def check_str(params):
  if not isinstance(params, str):
    raise ValueError('parameters must is string')
   
def check_index(index):
  if index < 5:
    raise IndexError('index must greater than 10')
 
class Super_List(list):
  def __init__(self):
    super(Super_List, self).__init__()   # 调用父类初始化
    self += [None] * 5           # 对初始化的参数进行修改
 
  def append(self, *args):          # 对append进行参数限制
    for i in args:
      check_str(i)
    return super(Super_List, self).append(*args)
 
  def insert(self, index, *args):       # 对insert的参数(索引及插入元素)进行限制
    check_index(index) # 判断插入位置
    for i in args:
      check_str(i)
    return super(Super_List, self).insert(index, *args)
 
  def extend(self, *args):          # 对扩张的列表元素进行判断
    temp = args[0]
    for i in temp:
      check_str(i)
    super(Super_List, self).extend(*args)
   
  def __delitem__(self, index):        # 对del命令的索引进行判断
    check_index(index)
    super(Super_List, self).__delitem__(index)
 
  def clear(self):              # 禁止使用clear命令
    raise TypeError('No permission')
 
ss_l = Super_List()
print(ss_l)
ss_l.append('1')
ss_l.insert(5, 'a')
ss_l.extend(['a', 'b', 'c'])
ss_l.clear()
print(ss_l)

编写耗时约半小时,由于列表的增删命令繁多,部分命令未重写,但核心逻辑保持一致。

若希望在访问参数时自动执行某些命令,可将相关逻辑写在 __getitem__ 下方。

更新后,添加 __missing__ 方法显得十分有趣。

class Dict(dict):
  def __init__(self, *args, **kwargs):
    # self.x = 12
    super(Dict, self).__init__(*args, **kwargs)
 
  def __missing__(self, key):
    self[key] = None
    return self[key]
l = Dict(((1,2),(2,3)))
print(l)
print(l[8])
print(l)
{1: 2, 2: 3}
None
{1: 2, 2: 3, 8: None}

这类似于字典的内置方法 setdefault,我尝试将其行为调整为一致。

代码已完成,现在可以通过 [] 符号进行取值操作。

# -*- coding: utf-8 -*-

class Dict(dict):
  def __init__(self, *args, **kwargs):
    super(Dict, self).__init__(*args, **kwargs)

  def __missing__(self, item):
    # 判断进来的参数是不是字符串,如果是字符串说明就是对象主动调用__missing__进来的
    # 非__getitem__导入的
    if isinstance(item, str):
      self[item] = 'Default Empty'
      return self[item]
    # 如果对象非字符串,明显说明是__getitem__导入的,判断长度就可以
    else:
      key, value = item
      self[key] = value   # 自身进行赋值
      return self[key]   # 返回value


  def __getitem__(self, item):
    if not isinstance(item, tuple):  # 传进来的item进行判断,如果非元祖,直接调用父类绑定self方法返回
      return super(Dict, self).__getitem__(item)
    elif len(item) == 2 and isinstance(item, tuple):  # 如果是元祖,又是2位长度的,进行赋值。其实感觉元祖判断没有好像也没关系
      k, _ = item
      if k in self:
        return super(Dict, self).__getitem__(k)   # 如果k在self里面继续调用父类绑定self方法返回
      else:
        res = self.__missing__(item)       # 否则调用自身的__missing
        return res
    else:
      raise TypeError('input pattern error')     # 元素数量超过2个,直接报错

l = Dict((('name','sidian'),('age',99)))

print(l)
print(l['name','wudian'])
print(l['addr','杭州'])
print(l['hobby'])
print(l)
{'name': 'sidian', 'age': 99}
sidian
杭州
Default Empty
{'name': 'sidian', 'age': 99, 'addr': '杭州', 'hobby': 'Default Empty'}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。