Python高级技术之:`Python`的`__setitem__`和`__getitem__`:如何实现类似`dict`的对象。

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的魔法方法还有很多,每一个都蕴藏着强大的力量。 只要你敢于探索,就能发现更多有趣的东西!

希望今天的讲座对你有所帮助! 咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注