本篇文章主要内容
代理类主要功能是将一个类实例的属性访问和控制代理到代码内部另外一个实例类,将想对外公布的属性的访问和控制权交给代理类来操作,保留不想对外公布的属性的访问或控制权,比如只读访问,日志功能
- 代理类实现被代理类的属性访问和修改权限控制
- 异常捕获代理类的简化示例
代理类的一个简单的实现方式示例
目标:实现类Product 的实例属性让另一个类Proxy 来代理访问和控制,想将对外公布的属性交给代理类让外部访问和控制,不想对外公布的属性无法通过代理来访问和控制,这里不想对外公布的属性约定用下划线命名开头
class Product:
def __init__(self, price, quantity):
self.price = price
self.quantity = quantity
self._current = 123
class Proxy:
def __init__(self, obj):
self._obj = obj
def __getattr__(self, item):
if item.startswith("_"):
raise Exception(f"{item} not found")
return getattr(self._obj, item)
def __setattr__(self, key, value):
if key.startswith("_"):
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)
def test_delattr():
p = Product(10, 2)
pp = Proxy(p)
pp.abc = 123
print(pp.abc, p.abc)
del pp.abc
pp._def = 123
print(pp._def)
测试获取属性
if __name__ == '__main__':
test_getattr()
输出:
10
...
Exception: _curr not found
...
测试设置属性
if __name__ == '__main__':
test_setattr()
输出
1 1
10000
...
AttributeError: 'Product' object has no attribute '_curr'
...
测试删除属性
if __name__ == '__main__':
test_delattr()
输出
123 123
123
注:以双下划线开头和结尾的方法无法被代理,想要使用,必须在代理类中定义出这个方法,然后重定向到被代理的类的方法,比如你想使用isinstance() 方法就要在Proxy 伪造定义__class__ 属性,想要使用len() 方法就要在Proxy 重定向返回到被代理的类的len方法
class Product:
def __init__(self, price, quantity):
self.price = price
self.quantity = quantity
self._current = 123
def __len__(self):
return 111
class Proxy:
def __init__(self, obj):
self._obj = obj
def __getattr__(self, item):
if item.startswith("_"):
raise Exception(f"{item} not found")
return getattr(self._obj, item)
def __setattr__(self, key, value):
if key.startswith("_"):
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))
def test_len():
p = Product(10, 2)
pp = Proxy(p)
print(len(pp))
测试伪造的实例class类型
if __name__ == '__main__':
test_instance()
输出
<class '__main__.Product'>
True
测试获取长度
if __name__ == '__main__':
test_len()
输出
111
一个实现日志输出的代理类的简化示例
捕获web server报错日志并执行异常处理的示例
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):
attr = getattr(self._obj, item)
if callable(attr):
@wraps(attr)
def wrapped_method(*args, **kwargs):
try:
method = attr(*args, **kwargs)
except ZeroDivisionError:
raise Exception(f"{attr.__name__} received a zero division error.")
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双下划线开头和结尾的方法,如果想要被代理类访问和控制,就必须在代理类中也定义对应的实际方法,另外,示例中主要是以下划线开头的方法作为私有属性的约定,也可以使用其他约定,这样在代理方法中的访问和修改时做出相应的判断即可
|