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的Tqdm模块的使用
Jan 10 Python
Python实现检测文件MD5值的方法示例
Apr 11 Python
python opencv3实现人脸识别(windows)
May 25 Python
详解python异步编程之asyncio(百万并发)
Jul 07 Python
详解Django中类视图使用装饰器的方式
Aug 12 Python
使用python打印十行杨辉三角过程详解
Jul 10 Python
Python使用指定字符长度切分数据示例
Dec 05 Python
pytorch如何冻结某层参数的实现
Jan 10 Python
对python中 math模块下 atan 和 atan2的区别详解
Jan 17 Python
Python生成器实现简单&quot;生产者消费者&quot;模型代码实例
Mar 27 Python
python爬虫泛滥的解决方法详解
Nov 25 Python
Python爬虫+tkinter界面实现历史天气查询的思路详解
Feb 22 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
Re:从零开始的异世界生活 第2季 开播啦
2020/07/24 日漫
PHP 模拟$_PUT实现代码
2010/03/15 PHP
php无限级分类实现方法分析
2016/10/19 PHP
php实现微信支付之现金红包
2018/05/30 PHP
PHP文件操作简单介绍及函数汇总
2020/12/11 PHP
jQuery 自动增长的文本输入框实现代码
2010/04/02 Javascript
jcarousellite.js 基于Jquery的图片无缝滚动插件
2010/12/30 Javascript
JQuery优缺点分析说明
2011/04/10 Javascript
使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
2015/08/21 NodeJs
javascript prototype原型详解(比较基础)
2016/12/26 Javascript
JS百度地图搜索悬浮窗功能
2017/01/12 Javascript
JS实现统计字符串中字符出现个数及最大个数功能示例
2018/06/04 Javascript
nodejs基础之buffer缓冲区用法分析
2018/12/26 NodeJs
Vue 使用formData方式向后台发送数据的实现
2019/04/14 Javascript
JS实现百度搜索框关键字推荐
2020/02/17 Javascript
JavaScript对象访问器Getter及Setter原理解析
2020/12/08 Javascript
[05:39]2014DOTA2国际邀请赛 DK晋级胜者组专访战队国士无双
2014/07/14 DOTA
从零学python系列之新版本导入httplib模块报ImportError解决方案
2014/05/23 Python
python通过colorama模块在控制台输出彩色文字的方法
2015/03/19 Python
python使用numpy读取、保存txt数据的实例
2018/10/14 Python
详解Python 解压缩文件
2019/04/09 Python
Python Flask 搭建微信小程序后台详解
2019/05/06 Python
通过PHP与Python代码对比的语法差异详解
2019/07/10 Python
Python生命游戏实现原理及过程解析(附源代码)
2019/08/01 Python
使用python实现CGI环境搭建过程解析
2020/04/28 Python
python与idea的集成的实现
2020/11/20 Python
[原创]赚疯了!转手立赚800+?大佬的python「抢茅台脚本」使用教程
2021/01/12 Python
html5各种页面切换效果和模态对话框用法总结
2014/12/15 HTML / CSS
JAKO-O德国野酷台湾站:德国首屈一指的婴幼童用品品牌
2019/01/14 全球购物
区三好学生主要事迹
2014/01/30 职场文书
个人贷款收入证明
2014/10/26 职场文书
世界地球日活动总结
2015/02/09 职场文书
出纳岗位职责范本
2015/03/31 职场文书
红色故事汇观后感
2015/06/18 职场文书
如何写好竞聘报告
2019/04/03 职场文书
Win11怎么解除儿童账号限制?Win11解除微软儿童账号限制方法
2022/07/07 数码科技