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


Posted in Python onMarch 21, 2022

本篇文章主要内容

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

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双下划线开头和结尾的方法,如果想要被代理类访问和控制,就必须在代理类中也定义对应的实际方法,另外,示例中主要是以下划线开头的方法作为私有属性的约定,也可以使用其他约定,这样在代理方法中的访问和修改时做出相应的判断即可

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注三水点靠木的更多内容! 

Python 相关文章推荐
Python操作Word批量生成文章的方法
Jul 28 Python
Python编程中装饰器的使用示例解析
Jun 20 Python
Python实现将罗马数字转换成普通阿拉伯数字的方法
Apr 19 Python
读取本地json文件,解析json(实例讲解)
Dec 06 Python
TensorFlow变量管理详解
Mar 10 Python
python 不以科学计数法输出的方法
Jul 16 Python
对python 多个分隔符split 的实例详解
Dec 20 Python
基于Python解密仿射密码
Oct 21 Python
pytorch的batch normalize使用详解
Jan 15 Python
python操作yaml说明
Apr 08 Python
matplotlib 画双轴子图无法显示x轴的解决方法
Jul 27 Python
python安装sklearn模块的方法详解
Nov 28 Python
python的netCDF4批量处理NC格式文件的操作方法
Python&Matlab实现灰狼优化算法的示例代码
Python学习之时间包使用教程详解
Mar 21 #Python
Python数据结构之队列详解
Python学习之os包使用教程详解
分享几种python 变量合并方法
Mar 20 #Python
python 使用tkinter与messagebox写界面和弹窗
Mar 20 #Python
You might like
php数组函数序列之array_key_exists() - 查找数组键名是否存在
2011/10/29 PHP
PHP中使用array函数新建一个数组
2015/11/19 PHP
Yii2简单实现给表单添加验证码的方法
2016/07/18 PHP
PHP基于mssql扩展远程连接MSSQL的简单实现方法
2016/10/08 PHP
PHP使用preg_split和explode分割textarea存放内容的方法分析
2017/07/03 PHP
Array.slice()与Array.splice()的返回值类型
2006/10/09 Javascript
javscript对象原型的一些看法
2010/09/19 Javascript
jquery中获取select选中值的代码
2011/06/27 Javascript
Javascript模块化编程(一)模块的写法最佳实践
2013/01/17 Javascript
使用Jquery实现点击文字后变成文本框且可修改
2013/09/21 Javascript
jQuery通过控制节点实现仅在前台通过get方法完成参数传递
2015/02/02 Javascript
javascript模拟map输出与去除重复项的方法
2015/02/09 Javascript
jquery.Jcrop结合JAVA后台实现图片裁剪上传实例
2016/11/05 Javascript
基于JQuery及AJAX实现名人名言随机生成器
2017/02/10 Javascript
在vue.js中抽出公共代码的方法示例
2017/06/08 Javascript
VUE 使用中踩过的坑
2018/02/08 Javascript
Vue.directive()的用法和实例详解
2018/03/04 Javascript
Vuejs+vue-router打包+Nginx配置的实例
2018/09/20 Javascript
js实现特别简单的钟表效果
2020/09/14 Javascript
[00:12]DAC2018 天才少年转战三号位,他的SOLO是否仍如昔日般强大?
2018/04/06 DOTA
[01:16:01]VGJ.S vs Mski Supermajor小组赛C组 BO3 第一场 6.3
2018/06/04 DOTA
MySQLdb ImportError: libmysqlclient.so.18解决方法
2014/08/21 Python
Python读写Json涉及到中文的处理方法
2016/09/12 Python
基于python的字节编译详解
2017/09/20 Python
tensorflow构建BP神经网络的方法
2018/03/12 Python
基于python3实现socket文件传输和校验
2018/07/28 Python
Python3+Pycharm+PyQt5环境搭建步骤图文详解
2019/05/29 Python
python opencv实现gif图片分解的示例代码
2019/12/13 Python
Python实现钉钉/企业微信自动打卡的示例代码
2021/02/02 Python
香蕉共和国Banana Republic官网:美国GAP旗下偏贵族风格服饰品牌
2016/11/21 全球购物
优秀求职信范文分享
2013/12/19 职场文书
市场营销毕业求职信
2014/08/07 职场文书
师德师风的心得体会
2014/09/02 职场文书
毕业论文致谢怎么写
2015/05/14 职场文书
Windows11性能真的上涨35%? 桌面酷睿i9实测结果公开
2021/11/21 数码科技
vue.js 使用原生js实现轮播图
2022/04/26 Vue.js