ZetCode

Python __setitem__ 方法

最后修改于 2025 年 4 月 8 日

这篇全面的指南探讨了 Python 的 __setitem__ 方法,这个特殊方法使得对象可以通过方括号进行下标操作。我们将涵盖基本用法、序列协议、映射协议和实际示例。

基本定义

__setitem__ 方法被调用来实现使用语法 obj[key] = value 对下标对象的赋值。它允许对象模拟容器类型。

主要特征:它必须接受至少两个参数(self 和 key),可以接受第三个 value 参数,并且针对切片操作会被调用。它能够在自定义类中实现可变序列和映射行为。

基本的 __setitem__ 实现

这是一个最简单的实现,展示了 __setitem__ 如何实现下标赋值。这演示了基本的语法和行为。

basic_setitem.py
class SimpleDict:
    def __init__(self):
        self._data = {}
    
    def __setitem__(self, key, value):
        print(f"Setting {key} to {value}")
        self._data[key] = value
    
    def __getitem__(self, key):
        return self._data[key]

d = SimpleDict()
d['name'] = 'Alice'  # Calls __setitem__
print(d['name'])     # Calls __getitem__

这个例子创建了一个简单的类似字典的类。__setitem__ 方法拦截方括号赋值并将它们存储在内部 dict 中。

输出显示了当我们赋值给 d['name'] 时该方法被调用。这种模式是在 Python 中创建自定义容器类型的基础。

实现一个序列类型

当创建支持索引赋值的可变序列类型时,__setitem__ 是必不可少的。这个例子展示了一个自定义列表的实现。

sequence.py
class MyList:
    def __init__(self, items=None):
        self._items = list(items) if items else []
    
    def __setitem__(self, index, value):
        if isinstance(index, slice):
            self._items[index] = value
        else:
            self._items[index] = value
    
    def __getitem__(self, index):
        return self._items[index]
    
    def __repr__(self):
        return repr(self._items)

lst = MyList([1, 2, 3])
lst[1] = 99       # Single index assignment
lst[0:2] = [7, 8] # Slice assignment
print(lst)        # [7, 8, 3]

这个自定义列表通过 __setitem__ 处理单个索引和切片赋值。该方法检查索引类型来处理这两种情况。

当在内部使用列表时,切片处理是自动的,但演示了 Python 如何将切片语法转换为 __setitem__ 调用。

创建一个受限的字典

__setitem__ 可以对可以分配的值强制执行约束。这个例子创建了一个只接受数值的字典。

restricted_dict.py
class NumericDict:
    def __init__(self):
        self._data = {}
    
    def __setitem__(self, key, value):
        if not isinstance(value, (int, float)):
            raise ValueError("Only numeric values allowed")
        self._data[key] = value
    
    def __getitem__(self, key):
        return self._data[key]

nd = NumericDict()
nd['age'] = 25    # Works
# nd['name'] = 'Bob'  # Raises ValueError
print(nd['age'])

这个字典子类在允许赋值之前验证值。__setitem__ 方法在存储值之前检查值类型。

这种模式对于创建受约束的容器非常有用,在这些容器中,您需要对存储的值强制执行特定的数据类型或验证规则。

具有 2D 索引的矩阵类

__setitem__ 可以通过接受元组作为键来处理多维索引。这个例子实现了一个简单的矩阵类。

matrix.py
class Matrix:
    def __init__(self, rows, cols):
        self.rows = rows
        self.cols = cols
        self._data = [[0] * cols for _ in range(rows)]
    
    def __setitem__(self, key, value):
        row, col = key
        if 0 <= row < self.rows and 0 <= col < self.cols:
            self._data[row][col] = value
        else:
            raise IndexError("Matrix indices out of range")
    
    def __getitem__(self, key):
        row, col = key
        return self._data[row][col]
    
    def __repr__(self):
        return '\n'.join(' '.join(map(str, row)) for row in self._data)

m = Matrix(3, 3)
m[1, 1] = 5  # Center cell
m[0, 2] = 3  # Top-right cell
print(m)

这个矩阵类接受使用元组符号的二维索引。 __setitem__ 将元组解包为行和列索引。

该方法包括边界检查,以确保索引在赋值之前有效。这演示了如何实现复杂的容器行为。

带有 __setitem__ 的代理模式

__setitem__ 可以用于实现代理模式,在这种模式中,对另一个对象的访问受到控制或修改。这个例子展示了一个日志代理。

proxy.py
class LoggingListProxy:
    def __init__(self, original):
        self._original = original
    
    def __setitem__(self, index, value):
        print(f"Setting index {index} to {value}")
        self._original[index] = value
    
    def __getitem__(self, index):
        return self._original[index]
    
    def __repr__(self):
        return repr(self._original)

original = [1, 2, 3]
proxy = LoggingListProxy(original)
proxy[1] = 99
print(original)  # [1, 99, 3]

这个代理包装了一个列表并记录所有赋值操作。实际的存储仍然发生在原始列表中,但我们拦截并记录这些操作。

代理模式对于添加诸如日志记录、验证或访问控制之类的行为非常有用,而无需修改原始对象的类。

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程