Python高级技术讲座:当__setitem__
和__getitem__
相遇,dict
都要抖三抖!
哈喽,大家好!我是今天的讲师,咱们今天不聊人生理想,就聊聊Python里面那些“扮猪吃老虎”的魔法方法。今天的主角是 __setitem__
和 __getitem__
,它们俩一出手,就能让你轻松打造一个和 dict
一样好用,甚至更好用的自定义对象。
1. dict
的魅力与局限
dict
(字典)是Python中最常用的数据结构之一,它的键值对存储方式简直不要太方便。 你想存个学生的名字和年龄? {'name': '张三', 'age': 18}
, 完事儿! 想找张三的年龄? my_dict['name']
, 一秒搞定!
但是,dict
也有它的局限性。 比如:
- 行为固定:
dict
的行为基本是固定的,你想在访问或设置值的时候做点额外的事情,就比较麻烦。 - 不能自定义验证: 你不能直接在
dict
里面加一些约束,比如要求所有的key必须是字符串,value必须是正整数。
所以,如果我们需要更灵活、更定制化的键值对存储方式,就得自己动手丰衣足食了!而__setitem__
和 __getitem__
这两个魔法方法,就是我们手中的利器。
2. __setitem__
和 __getitem__
:魔法的开端
这两个魔法方法是Python中实现容器类型(比如列表、字典)的关键。 简单来说:
__setitem__(self, key, value)
: 当你使用my_object[key] = value
这样的语法时,Python会自动调用my_object
的__setitem__
方法。__getitem__(self, key)
: 当你使用value = my_object[key]
这样的语法时,Python会自动调用my_object
的__getitem__
方法。
是不是感觉很简单? 别急,好戏还在后头呢!
3. 初试牛刀:打造一个简单的类MyDict
咱们先来写一个简单的类 MyDict
, 模拟 dict
的基本功能。
class MyDict:
def __init__(self):
self._data = {} # 用一个真正的 dict 来存储数据
def __setitem__(self, key, value):
print(f"正在设置 key: {key}, value: {value}")
self._data[key] = value
def __getitem__(self, key):
print(f"正在获取 key: {key} 的值")
return self._data[key]
# 测试一下
my_dict = MyDict()
my_dict['name'] = '李四' # 调用 __setitem__
age = my_dict['age'] = 20 # 调用 __setitem__和__getitem__
print(f"姓名: {my_dict['name']}, 年龄: {age}") # 调用 __getitem__
运行上面的代码,你会发现每次设置或获取值的时候,都会打印一些信息。 这就是 __setitem__
和 __getitem__
的威力,你可以在里面做任何你想做的事情!
4. 高级玩法:加入数据验证
现在,我们来给 MyDict
加上一些数据验证的功能。 比如,我们要求所有的 key 必须是字符串,value 必须是正整数。
class MyDict:
def __init__(self):
self._data = {}
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError("Key must be a string")
if not isinstance(value, int) or value <= 0:
raise ValueError("Value must be a positive integer")
print(f"正在设置 key: {key}, value: {value}")
self._data[key] = value
def __getitem__(self, key):
print(f"正在获取 key: {key} 的值")
return self._data[key]
# 测试一下
my_dict = MyDict()
try:
my_dict[123] = 'abc' # 抛出 TypeError
except Exception as e:
print(f"出错了: {e}")
try:
my_dict['age'] = -10 # 抛出 ValueError
except Exception as e:
print(f"出错了: {e}")
my_dict['age'] = 25 # 正常设置
print(my_dict['age']) # 正常获取
现在,如果尝试设置不符合规则的数据,就会抛出异常。 这就实现了数据验证的功能。
5. 更上一层楼:实现默认值
有时候,我们希望在访问一个不存在的 key 时,返回一个默认值,而不是抛出 KeyError
。 这也可以通过 __getitem__
来实现。
class MyDict:
def __init__(self, default_value=None):
self._data = {}
self._default_value = default_value
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError("Key must be a string")
if not isinstance(value, int) or value <= 0:
raise ValueError("Value must be a positive integer")
print(f"正在设置 key: {key}, value: {value}")
self._data[key] = value
def __getitem__(self, key):
print(f"正在获取 key: {key} 的值")
if key in self._data:
return self._data[key]
else:
return self._default_value # 返回默认值
# 测试一下
my_dict = MyDict(default_value=0)
print(my_dict['score']) # 返回 0
my_dict['score'] = 90
print(my_dict['score']) # 返回 90
现在,如果访问一个不存在的 score
,就会返回我们设置的默认值 0。
6. 进阶:实现属性访问
除了像 dict
一样用 []
来访问,我们还可以让 MyDict
支持像访问属性一样来访问数据。 这需要用到 __getattr__
和 __setattr__
这两个魔法方法。
class MyDict:
def __init__(self):
self._data = {}
def __setitem__(self, key, value):
self._data[key] = value
def __getitem__(self, key):
return self._data[key]
def __setattr__(self, name, value):
# 拦截属性设置,将其存储到 _data 字典中
if name.startswith('_'): # 避免递归调用
super().__setattr__(name, value) # 调用父类的__setattr__
else:
self._data[name] = value
def __getattr__(self, name):
# 拦截属性访问,从 _data 字典中获取值
try:
return self._data[name]
except KeyError:
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
# 测试一下
my_dict = MyDict()
my_dict.name = '王五' # 调用 __setattr__
my_dict.age = 30 # 调用 __setattr__
print(my_dict.name) # 调用 __getattr__
print(my_dict.age) # 调用 __getattr__
print(my_dict['name']) # 调用 __getitem__
try:
print(my_dict.address) # 调用 __getattr__
except AttributeError as e:
print(e)
这里需要注意几个点:
__setattr__
和__getattr__
会拦截所有的属性设置和访问,所以要小心使用,避免出现意想不到的问题。- 在
__setattr__
里面,如果要设置实例的属性,一定要调用super().__setattr__(name, value)
,否则会陷入无限递归。 __getattr__
在属性不存在时才会被调用,如果属性存在,是不会调用__getattr__
的。
7. 终极形态:一个功能更强大的 MyDict
现在,我们把上面的所有功能都整合起来,打造一个功能更强大的 MyDict
。
class MyDict:
def __init__(self, default_value=None, key_type=str, value_type=int):
self._data = {}
self._default_value = default_value
self._key_type = key_type
self._value_type = value_type
def __setitem__(self, key, value):
if not isinstance(key, self._key_type):
raise TypeError(f"Key must be a {self._key_type.__name__}")
if not isinstance(value, self._value_type):
raise ValueError(f"Value must be a {self._value_type.__name__}")
print(f"正在设置 key: {key}, value: {value}")
self._data[key] = value
def __getitem__(self, key):
print(f"正在获取 key: {key} 的值")
if key in self._data:
return self._data[key]
else:
return self._default_value
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value)
else:
self[name] = value # 使用 __setitem__ 进行设置
def __getattr__(self, name):
try:
return self[name] # 使用 __getitem__ 进行获取
except KeyError:
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
def __delitem__(self, key):
print(f"正在删除key: {key}")
del self._data[key]
def __contains__(self, key):
print(f"正在检查是否包含key: {key}")
return key in self._data
def __len__(self):
return len(self._data)
def __iter__(self):
return iter(self._data)
def keys(self):
return self._data.keys()
def values(self):
return self._data.values()
def items(self):
return self._data.items()
# 测试一下
my_dict = MyDict(default_value=0, key_type=str, value_type=int)
my_dict.age = 25
print(my_dict.age) # 25
print(my_dict['age']) # 25
print(len(my_dict))
print(my_dict.keys())
del my_dict['age']
print(len(my_dict))
这个 MyDict
已经非常强大了,它支持:
- 自定义默认值
- 自定义key和value的类型
- 属性访问
del my_dict[key]
操作in my_dict
操作len(my_dict)
操作- 迭代操作
- keys(), values(), items() 方法
8. 总结
通过 __setitem__
和 __getitem__
这两个魔法方法,我们可以轻松地自定义键值对存储的行为,实现数据验证、默认值、属性访问等功能。 这些魔法方法可以帮助我们打造更灵活、更强大的数据结构,满足各种各样的需求。
当然,这只是冰山一角。Python的魔法方法还有很多,每一个都蕴藏着强大的力量。 只要你敢于探索,就能发现更多有趣的东西!
希望今天的讲座对你有所帮助! 咱们下次再见!