Python的代理类实现,控制访问和修改属性的权限你都了解吗

作者:Moelimoe 时间:2022-10-25 13:09:47 

本篇文章主要内容

代理类主要功能是将一个类实例的属性访问和控制代理到代码内部另外一个实例类,将想对外公布的属性的访问和控制权交给代理类来操作,保留不想对外公布的属性的访问或控制权,比如只读访问,日志功能

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.

来源:https://blog.csdn.net/Moelimoe/article/details/123609039

标签:Python,代理类实现,控制访问,修改属性
0
投稿

猜你喜欢

  • Python webargs 模块的简单使用

    2021-02-27 11:56:04
  • PYTHON绘制雷达图代码实例

    2021-12-17 01:33:58
  • python 将字符串转换成字典dict的各种方式总结

    2022-06-28 21:13:35
  • mysql 5.7.14 下载安装、配置与使用详细教程

    2024-01-15 14:39:25
  • python 用pandas实现数据透视表功能

    2022-04-23 02:49:40
  • 浅谈Go语言中的次方用法

    2024-02-17 04:57:08
  • Opencv+Python实现图像运动模糊和高斯模糊的示例

    2022-08-06 12:25:19
  • ASP中使用FileSystemObject时提高性能的一个小技巧

    2008-06-08 13:09:00
  • python特效之字符成像详解

    2021-06-07 02:18:45
  • jQuery 1.4新特性及其变化(上)

    2010-01-18 16:33:00
  • python 基本结构语句(函数和模块)

    2023-06-14 00:37:56
  • 对python插入数据库和生成插入sql的示例讲解

    2022-03-10 05:46:40
  • Oracle如何直接运行OS命令(上)第1/2页

    2010-07-30 12:54:00
  • ASP 自动采集实现代码

    2011-03-07 11:17:00
  • python爬取一组小姐姐图片实例

    2023-08-03 15:05:45
  • vue中@keyup.enter失效问题及解决

    2023-07-02 17:01:38
  • 用户凭什么跟你注册?

    2011-06-10 13:16:00
  • 解决python3 urllib 链接中有中文的问题

    2022-10-19 08:43:51
  • 分组字符合并SQL语句 按某字段合并字符串之一(简单合并)

    2024-01-24 11:30:20
  • sqlserver找回企业管理器的方法

    2024-01-27 08:41:54
  • asp之家 网络编程 m.aspxhome.com