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 Queue模块详解
Nov 30 Python
利用Python和OpenCV库将URL转换为OpenCV格式的方法
Mar 27 Python
使用beaker让Facebook的Bottle框架支持session功能
Apr 23 Python
利用pandas读取中文数据集的方法
Jul 25 Python
对numpy中的transpose和swapaxes函数详解
Aug 02 Python
神经网络相关之基础概念的讲解
Dec 29 Python
浅谈python中get pass用法
Mar 19 Python
使用Python计算玩彩票赢钱概率
Jun 26 Python
python3.6 如何将list存入txt后再读出list的方法
Jul 02 Python
解决python调用自己文件函数/执行函数找不到包问题
Jun 01 Python
Pycharm新手使用教程(图文详解)
Sep 17 Python
OpenCV实现常见的四种图像几何变换
Apr 01 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 采集程序 常用函数
2008/12/18 PHP
PHP之生成GIF动画的实现方法
2013/06/07 PHP
discuz免激活同步登入代码修改方法(discuz同步登录)
2013/12/24 PHP
在Laravel5.6中使用Swoole的协程数据库查询
2018/06/15 PHP
PHP删除数组中指定值的元素常用方法实例分析【4种方法】
2018/08/21 PHP
JQUERY获取form表单值的代码
2010/07/17 Javascript
javascript下利用arguments实现string.format函数
2010/08/24 Javascript
学习并汇集javascript匿名函数
2010/11/25 Javascript
js获取某月的最后一天日期的简单实例
2013/06/22 Javascript
javascript贪吃蛇完整版(源码)
2013/12/09 Javascript
jQuery中DOM树操作之使用反向插入方法实例分析
2015/01/23 Javascript
JavaScript判断表单提交时哪个radio按钮被选中的方法
2015/03/21 Javascript
JS拖拽组件学习使用
2016/01/19 Javascript
IOS中safari下的select下拉菜单文字过长不换行的解决方法
2016/09/26 Javascript
JavaScript实现的XML与JSON互转功能详解
2017/02/16 Javascript
微信小程序 PHP生成带参数二维码
2017/02/21 Javascript
vue基于Element构建自定义树的示例代码
2017/09/19 Javascript
Vue退出登录时清空缓存的实现
2019/11/12 Javascript
JavaScript设计模式之门面模式原理与实现方法分析
2020/03/09 Javascript
使用JavaScript和MQTT开发物联网应用示例解析
2020/08/07 Javascript
vue 实现一个简单的全局调用弹窗案例
2020/09/10 Javascript
vue项目打包后请求地址错误/打包后跨域操作
2020/11/04 Javascript
[03:43]TI9战队采访——PSG.LGD
2019/08/22 DOTA
python 基础教程之Map使用方法
2017/01/17 Python
python 每天如何定时启动爬虫任务(实现方法分享)
2018/05/21 Python
Python实现两个list求交集,并集,差集的方法示例
2018/08/02 Python
利用python 读写csv文件
2020/09/10 Python
Onzie官网:美国时尚瑜伽品牌
2019/08/21 全球购物
如何提高MySql的安全性
2014/06/19 面试题
药品促销活动方案
2014/02/14 职场文书
ktv好的活动方案
2014/08/17 职场文书
2014年教师节演讲稿范文
2014/09/10 职场文书
趣味运动会简讯
2015/07/20 职场文书
学校标语口号大全
2015/12/26 职场文书
2019年新郎保证书3篇
2019/10/17 职场文书
前端JS获取URL参数的4种方法总结
2022/04/05 Javascript