Python __setitem__() 语法
__setitem__() 是 Python 的一个特殊方法(又称 “魔法方法” 或 “双下划线方法”),它用于定义当尝试通过索引或键为对象的元素赋值时(例如 obj[key] = value),其行为是怎样的。
语法:
class MyContainer:
def __setitem__(self, key, value):
# 根据 key 设置对应的 value
if isinstance(key, int):
# 处理整数索引的赋值
self._data[key] = value
else:
# 处理其他类型的键的赋值(例如,字符串键,如果对象像字典)
self._data[key] = value
说明:
__setitem__() 方法接收以下 3 个参数。
self(必选):约定俗成的名称,表示实例对象。key(必选):表示用于设置元素的键,它的类型取决于赋值方式:- 如果是 obj[index] = value,key 将是一个整数。
- 如果是 obj['name'] = value,key 将是一个字符串。
- 如果是 obj[start:stop:step] = value,key 将是一个 slice 对象。
value(必选):要赋给 key 对应位置的新值。
__setitem__() 方法不返回任何值(隐式返回 None)。如果 key 无效或操作不允许,应抛出相应的异常(如 IndexError、KeyError 或 TypeError)。
提示:如果一个类实现了 __setitem__() 方法,那么它的实例就支持使用 “[]” 操作符进行元素赋值和切片赋值。
Python __setitem__() 摘要
| 属于 | Python 魔法方法 |
|---|---|
| 使用频率 | 中 |
| 官方文档 | 查看 |
| 相关方法 | __getitem__()、__delitem__() |
Python __setitem__() 示例
接下来,我们通过几个简单的例子来讲解一下 Python __setitem__() 方法是如何使用的。
示例 1:__setitem__() 实现索引赋值
class MySequence:
def __init__(self, initial_data):
self._data = list(initial_data)
def __getitem__(self, index):
return self._data[index]
def __setitem__(self, index, value):
# 实现通过索引修改元素
if not isinstance(index, int):
raise TypeError('索引必须是整数')
if not (0 <= index < len(self._data)):
raise IndexError('索引超出范围')
self._data[index] = value
def __len__(self):
return len(self._data)
def __repr__(self):
return f'MySequence({self._data})'
# 创建实例
seq = MySequence(['red', 'green', 'blue'])
print(seq)
# 通过索引赋值
seq[1] = 'yellow'
print(seq)
# 尝试修改不存在的索引
try:
seq[3] = 'purple'
except IndexError as e:
print(e)
# 尝试使用非整数索引
try:
seq['color'] = 'orange'
except TypeError as e:
print(e)
运行结果如下。
MySequence(['red', 'green', 'blue'])
MySequence(['red', 'yellow', 'blue'])
(报错)索引超出范围
(报错)索引必须是整数
分析:
在 Sequence 类中,我们手动实现了 __setitem__() 方法。该方法用于使得其实例能够像普通列表一样使用整数索引进行赋值。
当 seq[1] = 'yellow' 被执行时,__setitem__() 方法被触发,接收 “1” 作为 index 参数,'yellow' 作为 value 参数,并将其设置到内部列表的对应位置。
示例 2:__setitem__() 实现切片赋值
class MySequence:
def __init__(self, data):
self._items = list(data)
def __getitem__(self, key):
return self._items[key]
def __setitem__(self, key, value):
# 实现通过索引或切片赋值
if isinstance(key, slice):
# 确保切片赋值的值是可迭代的
self._items[key] = list(value)
elif isinstance(key, int):
self._items[key] = value
else:
raise TypeError(f'不支持的键类型: {type(key)}')
def __len__(self):
return len(self._items)
def __repr__(self):
return f'MySequence({self._items})'
# 原始数据
seq = MySequence([10, 20, 30, 40, 50, 60])
print(seq)
# 索引赋值
seq[0] = 5
print(seq)
# 切片赋值 (替换部分)
seq[1:3] = [200, 300]
print(seq)
运行结果如下。
MySequence([10, 20, 30, 40, 50, 60])
MySequence([5, 20, 30, 40, 50, 60])
MySequence([5, 200, 300, 40, 50, 60])
分析:
__setitem__() 同样可以处理切片对象,从而实现切片赋值功能。在这个例子中,当我们使用切片语法(如 seq[1:3] = [200, 300])时,Python 会将切片表达式转换为一个 slice 对象,并将其作为 key 参数传递给 __setitem__()。然后我们可以在 __setitem__() 内部通过判断 key 的类型,来分别处理 “索引赋值” 和 “切片赋值” 。
需要注意的是,当切片包含步长时,赋值列表的长度必须与切片对应的元素数量一致,否则会引发 ValueError 异常。
示例 3:__setitem__() 实现字典风格的键值设置
class Settings:
def __init__(self):
self._config = {}
def __getitem__(self, key):
return self._config.get(key, None) # 使用 get 返回 None 而不是抛 KeyError
def __setitem__(self, key, value):
# 实现通过键设置配置项
if not isinstance(key, str):
raise TypeError('设置键必须是字符串')
self._config[key] = value
def __len__(self):
return len(self._config)
def __repr__(self):
return f'Settings({self._config})'
app = Settings()
print(app)
# 字典风格设置键值
app['database_url'] = 'sqlite:///app.db'
app['debug_mode'] = True
print(app)
# 修改现有键的值
app['debug_mode'] = False
print(app)
运行结果如下。
Settings({})
Settings({'database_url': 'sqlite:///app.db', 'debug_mode': True})
Settings({'database_url': 'sqlite:///app.db', 'debug_mode': False})
分析:
__setitem__() 也可以用于实现字典风格的键值设置。在这个例子中,当我们使用 app_settings['database_url'] = 'sqlite:///app.db' 时,__setitem__() 会被调用,并接收 'database_url' 作为 key,'sqlite:///app.db' 作为 value。
通过上面例子这种方式,我们可以创建自定义的映射类型,支持类似字典的赋值语法。
示例 4:对不可修改的自定义对象进行赋值
class MyObject:
def __init__(self, data):
self._data = tuple(data) # 内部使用元组,使其不可变
def __getitem__(self, index):
return self._data[index]
def __len__(self):
return len(self._data)
def __repr__(self):
return f'MyObject{self._data}'
obj = MyObject([1, 2, 3])
print(obj)
# 尝试对不可变对象进行赋值
try:
obj[0] = 100
except TypeError as e:
print(e)
运行结果如下。
MyObject(1, 2, 3)
'MyObject' object does not support item assignment
分析:
如果一个自定义对象在设计上是不可修改的,但我们尝试对它进行元素赋值,Python 就会抛出 TypeError 异常。
