Python的代理类实现,控制访问和修改属性的权限你都了解吗
作者:Moelimoe
本篇文章主要内容
代理类主要功能是将一个类实例的属性访问和控制代理到代码内部另外一个实例类,将想对外公布的属性的访问和控制权交给代理类来操作,保留不想对外公布的属性的访问或控制权,比如只读访问,日志功能
1.代理类实现被代理类的属性访问和修改权限控制
2.异常捕获代理类的简化示例
代理类的一个简单的实现方式示例
目标:实现类Product
的实例属性让另一个类Proxy
来代理访问和控制,想将对外公布的属性交给代理类让外部访问和控制,不想对外公布的属性无法通过代理来访问和控制,这里不想对外公布的属性约定用下划线命名开头
# proxy_example1.py # 以下是一个代理类实现只读访问的示例 # 目标:代理后只能访问和修改Product的公开属性,私有属性_current只能查看不能修改 class Product: def __init__(self, price, quantity): self.price = price self.quantity = quantity self._current = 123 # 只暴露代理类Proxy给外部使用 class Proxy: def __init__(self, obj): self._obj = obj def __getattr__(self, item): # 本实例没有找到的属性会执行__getattr__方法 if item.startswith("_"): # 约定下划线开头的方法不能访问到被代理的类,只会访问到代理类 raise Exception(f"{item} not found") # Product存在的私有属性也不希望被外部知道 return getattr(self._obj, item) def __setattr__(self, key, value): if key.startswith("_"): # 约定下划线开头的方法不能访问到被代理的类,只会访问到代理类 # 注:这里不能raise,这会导致Proxy的实例都无法创建(__dict__等属性无法创建) super(Proxy, self).__setattr__(key, value) # 避免无限循环 else: setattr(self._obj, key, value) # 要求只能删除非下划线开头的属性 def __delattr__(self, item): if item.startswith("_"): super(Proxy, self).__delattr__(item) # 避免无限循环 else: delattr(self._obj, item) def test_getattr(): p = Product(10, 1) pp = Proxy(p) print(pp.price) print(pp._curr) def test_setattr(): p = Product(10, 2) pp = Proxy(p) pp.abc = 1 print(pp.abc, p.abc) pp._curr = 10000 print(pp._curr) # 私有属性,设置给了代理类 print(p._curr) # raise an error, 被代理的类Product的属性没有设置成功也无法访问 def test_delattr(): p = Product(10, 2) pp = Proxy(p) pp.abc = 123 print(pp.abc, p.abc) # 删除公开属性 del pp.abc # 成功 # print(pp.abc, p.abc) # 已被删除 # # 删除私有属性 # del pp._curr # 会尝试删除Proxy的私有属性,raise AttributeError: _curr # 先创建在删除 pp._def = 123 # 这个操作只会设置Proxy的实例属性 print(pp._def) # 访问的是Proxy实例属性,被代理的Product实例没有创建_def属性 # del pp._def # 删除的是Proxy的实例属性 # print(pp._def)
测试获取属性
if __name__ == '__main__': test_getattr()
输出:
10
...
Exception: _curr not found
...
测试设置属性
if __name__ == '__main__': test_delattr()
输出
1 1
10000
...
AttributeError: 'Product' object has no attribute '_curr'
...
测试删除属性
if __name__ == '__main__': test_delattr()
输出
123 123
123
注:以双下划线开头和结尾的方法无法被代理,想要使用,必须在代理类中定义出这个方法,然后重定向到被代理的类的方法,比如你想使用isinstance()
方法就要在Proxy
伪造定义__class__
属性,想要使用len()
方法就要在Proxy
重定向返回到被代理的类的len方法
# proxy_example2.py class Product: def __init__(self, price, quantity): self.price = price self.quantity = quantity self._current = 123 def __len__(self): return 111 # 只暴露代理类Proxy给外部使用 class Proxy: def __init__(self, obj): self._obj = obj def __getattr__(self, item): # 本实例没有找到的属性会执行__getattr__方法 if item.startswith("_"): # 约定下划线开头的方法不能访问到被代理的类,只会访问到代理类 raise Exception(f"{item} not found") # Product存在的私有属性也不希望被外部知道 return getattr(self._obj, item) def __setattr__(self, key, value): if key.startswith("_"): # 约定下划线开头的方法不能访问到被代理的类,只会访问到代理类 # 注:这里不能raise,这会导致Proxy的实例都无法创建(__dict__等属性无法创建) super(Proxy, self).__setattr__(key, value) # 避免无限循环 else: setattr(self._obj, key, value) # 要求只能删除非下划线开头的属性 def __delattr__(self, item): if item.startswith("_"): super(Proxy, self).__delattr__(item) # 避免无限循环 else: delattr(self._obj, item) @property def __class__(self): # 伪造类 return self._obj.__class__ def __len__(self): return len(self._obj) def test_instance(): p = Product(10, 2) pp = Proxy(p) print(pp.__class__) print(isinstance(pp, Product)) # 如果不伪造__class__,会返回False def test_len(): p = Product(10, 2) pp = Proxy(p) print(len(pp)) # 如果Proxy实例不定义__len__方法,会报错TypeError: object of type 'Proxy' has no len()
测试伪造的实例class类型
if __name__ == '__main__': test_instance()
输出
<class '__main__.Product'>
True
测试获取长度
if __name__ == '__main__': test_len()
输出
111
一个实现日志输出的代理类的简化示例
捕获web server报错日志并执行异常处理的示例
# logger_proxy.py # -*- coding:utf-8 -*- from functools import wraps class DAL: @classmethod def dm1(cls, req, *args): print("dm1...", f"{req=}") print(1/0) # 故意抛出异常 return "dm1" class BLL: @classmethod def bm1(cls, req): print("bm1...", f"{req=}") return DAL.dm1(req) class Application: def __init__(self, req): self.req = req self._p = "private attr" def hd1(self): return BLL.bm1(self.req) class LoggerProxy: def __init__(self, obj): self._obj = obj def __getattr__(self, item): # LoggerProxy类实例没获取到的属性会执行这个方法 attr = getattr(self._obj, item) if callable(attr): # 获取到了方法,则处理异常捕获 @wraps(attr) def wrapped_method(*args, **kwargs): # print(f"Before access to attribute/method: {item}") try: method = attr(*args, **kwargs) except ZeroDivisionError: # 捕获异常然后处理... raise Exception(f"{attr.__name__} received a zero division error.") # print(f"After attribute/method {item} returned") return method return wrapped_method else: # 获取到了属性,直接返回 return attr if __name__ == '__main__': lp = LoggerProxy(Application("abc")) print(lp.req) print(lp._p) print(lp.hd1())
运行输出
abc
private attr
bm1... req='abc'
dm1... req='abc'
Traceback...
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback...
Exception: hd1 received a zero division error.
总结
本节主要的内容是实现了一个代理类,达到代理访问和控制某个类的属性并避免将私有属性暴露给外部,需要注意的是,一些特殊方法,也就是python双下划线开头和结尾的方法,如果想要被代理类访问和控制,就必须在代理类中也定义对应的实际方法,另外,示例中主要是以下划线开头的方法作为私有属性的约定,也可以使用其他约定,这样在代理方法中的访问和修改时做出相应的判断即可
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!