如何防止Python绕过setter修改类字典属性,或触发校验?
问题1:能否阻止直接修改字典属性的行为?
可以阻止,核心是不让外部获取到原生的可修改容器对象,而是返回受限制的包装类或只读视图,常见实现方式有两种:
- 使用只读视图:用
types.MappingProxyType包装字典,外部仅能读取,无法直接修改键值对;对于嵌套的列表,可替换为tuple(不可变)或自定义只读列表类,拦截所有修改方法。 - 自定义受限容器类:继承
dict或list,重写__setitem__、append、__delitem__等修改类方法,直接抛出禁止修改的异常,强制用户只能通过类提供的方法(如add_item)操作数据。
示例代码(基于Bookcase场景,用只读视图实现):
import types class Book: def __init__(self, title, weight): self.title = title self.weight = weight class Bookcase: def __init__(self): # 内部用原生容器存储数据 self._shelves = { 1: {'books': [], 'max_weight': 10, 'current_weight': 0} } @property def shelves(self): # 返回字典的只读视图,外部无法修改字典结构 read_only_shelves = types.MappingProxyType(self._shelves) # 进一步将每个书架的books转为只读列表 for shelf in read_only_shelves.values(): shelf['books'] = tuple(shelf['books']) return read_only_shelves def add_book(self, shelf_num, book): shelf = self._shelves.get(shelf_num) if not shelf: raise ValueError(f"Shelf {shelf_num} does not exist") if shelf['current_weight'] + book.weight > shelf['max_weight']: raise ValueError(f"Adding {book.title} exceeds shelf {shelf_num} weight limit") shelf['books'].append(book) shelf['current_weight'] += book.weight
此时用户执行library.shelves[1]['books'].append(big_book)会抛出AttributeError,因为tuple没有append方法,彻底阻止了绕过行为。
问题2:能否通过魔术方法触发
add_item? 可以通过自定义容器类并重写对应魔术方法,拦截所有修改操作,自动触发校验逻辑。针对嵌套结构,需要分别处理字典和列表的修改方法:
- 自定义字典类,重写
__setitem__拦截键值对修改; - 自定义列表类,重写
append、extend等方法拦截元素添加; - 将自定义容器与原类关联,在修改方法中调用原类的校验逻辑(或直接复用
add_item方法)。
示例代码(基于Bookcase场景):
class Book: def __init__(self, title, weight): self.title = title self.weight = weight class BookList(list): def __init__(self, parent_shelf, *args): super().__init__(*args) self.parent_shelf = parent_shelf # 关联所属书架 def append(self, book): # 触发重量校验,逻辑与add_book一致 if self.parent_shelf['current_weight'] + book.weight > self.parent_shelf['max_weight']: raise ValueError(f"Adding {book.title} exceeds shelf weight limit") super().append(book) self.parent_shelf['current_weight'] += book.weight # 如需支持其他添加方法,如extend、insert,需同理重写 class ShelveDict(dict): def __init__(self, parent_bookcase, *args): super().__init__(*args) self.parent_bookcase = parent_bookcase # 关联所属书柜 def __setitem__(self, key, value): # 确保books只能是BookList实例,避免被替换为普通列表 if key == 'books' and not isinstance(value, BookList): raise TypeError("Books must be stored in BookList") super().__setitem__(key, value) class Bookcase: def __init__(self): self.shelves = {} self.add_shelf(1, max_weight=10) def add_shelf(self, shelf_num, max_weight): shelf = ShelveDict(self) shelf['books'] = BookList(shelf) shelf['max_weight'] = max_weight shelf['current_weight'] = 0 self.shelves[shelf_num] = shelf def add_book(self, shelf_num, book): # 复用BookList的append逻辑,避免代码重复 self.shelves[shelf_num]['books'].append(book)
此时用户无论是调用library.add_book(1, big_book)还是直接执行library.shelves[1]['books'].append(big_book),都会触发重量校验,无法绕过限制。
内容的提问来源于stack exchange,提问作者Joshua Mincer
相关产品推荐
相关产品推荐

