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 version 2.7 required, which was not found in the registry
Aug 26 Python
python实现简单的TCP代理服务器
Oct 08 Python
Python+Socket实现基于UDP协议的局域网广播功能示例
Aug 31 Python
使用Python监控文件内容变化代码实例
Jun 04 Python
Python全排列操作实例分析
Jul 24 Python
Python零基础入门学习之输入与输出
Apr 03 Python
python使用Qt界面以及逻辑实现方法
Jul 10 Python
Python中的四种交换数值的方法解析
Nov 18 Python
Anaconda的安装及其环境变量的配置详解
Apr 22 Python
Python获取浏览器窗口句柄过程解析
Jul 25 Python
Python列表嵌套常见坑点及解决方案
Sep 30 Python
Python GUI之tkinter窗口视窗教程大集合(推荐)
Oct 20 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实现下载功能的代码
2012/09/29 PHP
PHP面向对象五大原则之里氏替换原则(LSP)详解
2018/04/08 PHP
PHP替换Word中变量并导出PDF图片的实现方法
2020/11/26 PHP
基于Jquery插件开发之图片放大镜效果(仿淘宝)
2011/11/19 Javascript
使用jquery hover事件实现表格的隔行换色功能示例
2013/09/03 Javascript
addEventListener 的用法示例介绍
2014/05/07 Javascript
基于jquery实现的可编辑下拉框实现代码
2014/08/02 Javascript
window.onload与$(document).ready()的区别分析
2015/05/30 Javascript
JS实现仿腾讯微博无刷新删除微博效果代码
2015/10/16 Javascript
jQuery简单获取键盘事件的方法
2016/01/22 Javascript
BootStrap Progressbar 实现大文件上传的进度条的实例代码
2016/06/27 Javascript
jquery自定义表单验证插件
2016/10/12 Javascript
H5图片压缩与上传实例
2017/04/21 Javascript
基于JS实现网页中的选项卡(两种方法)
2017/06/16 Javascript
浅谈node.js 命令行工具(cli)
2018/05/10 Javascript
对vuex中getters计算过滤操作详解
2019/11/06 Javascript
解决element-ui的下拉框有值却无法选中的情况
2020/11/07 Javascript
Python实现以时间换空间的缓存替换算法
2016/02/19 Python
不归路系列:Python入门之旅-一定要注意缩进!!!(推荐)
2019/04/16 Python
Python Django中的STATIC_URL 设置和使用方式
2020/03/27 Python
使用python检查yaml配置文件是否符合要求
2020/04/09 Python
python正则表达式re.match()匹配多个字符方法的实现
2021/01/27 Python
乌克兰网上服装店:Bolf.ua
2018/10/30 全球购物
日本必酷网络直营店:Biccamera
2019/03/23 全球购物
工程招投标邀请书
2014/01/26 职场文书
幼儿园教育教学反思
2014/01/31 职场文书
法制宣传实施方案
2014/03/13 职场文书
2014年大学生就业规划书
2014/04/04 职场文书
2015年师德师风自我评价范文
2015/03/05 职场文书
会计简历自我评价
2015/03/10 职场文书
2015庆祝七一建党节94周年活动总结
2015/03/20 职场文书
2016入党积极分子党校培训心得体会
2016/01/06 职场文书
如何让2019年上半年的工作总结更出色!
2019/07/01 职场文书
该怎么书写道歉信?
2019/07/03 职场文书
对Keras自带Loss Function的深入研究
2021/05/25 Python
一篇文章弄懂Python中的内建函数
2021/08/07 Python