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中使用poplib模块收取邮件的教程
Apr 29 Python
Python中断言Assertion的一些改进方案
Oct 27 Python
python解决汉字编码问题:Unicode Decode Error
Jan 19 Python
Python多继承顺序实例分析
May 26 Python
Python使用tkinter库实现文本显示用户输入功能示例
May 30 Python
用Pelican搭建一个极简静态博客系统过程解析
Aug 22 Python
Python3标准库glob文件名模式匹配的问题
Mar 13 Python
python将音频进行变速的操作方法
Apr 08 Python
python 下载文件的多种方法汇总
Nov 17 Python
java字符串格式化输出实例讲解
Jan 06 Python
浅谈Python数学建模之整数规划
Jun 23 Python
http通过StreamingHttpResponse完成连续的数据传输长链接方式
Feb 12 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编程效率 引入缓存机制提升性能
2010/02/15 PHP
九个你必须知道而且又很好用的php函数和特点
2013/08/08 PHP
PHP输入流php://input实例讲解
2015/12/22 PHP
PHP折半(二分)查找算法实例分析
2018/05/12 PHP
PHP实现防止表单重复提交功能【基于token验证】
2018/05/24 PHP
PHP验证类的封装与使用方法详解
2019/01/10 PHP
在thinkphp5.0路径中实现去除index.php的方式
2019/10/16 PHP
Laravel 微信小程序后端实现用户登录的示例代码
2019/11/26 PHP
Javascript 两个窗体之间传值实现代码
2009/09/25 Javascript
jQuery1.6 类型判断实现代码
2011/09/01 Javascript
javascript中attribute和property的区别详解
2014/06/05 Javascript
JavaScript字符串对象split方法入门实例(用于把字符串分割成数组)
2014/10/16 Javascript
NodeJS和BootStrap分页效果的实现代码
2016/11/07 NodeJs
使用mint-ui开发项目的一些心得(分享)
2017/09/07 Javascript
关于angularJs清除浏览器缓存的方法
2017/11/28 Javascript
redux-saga 初识和使用
2018/03/10 Javascript
jQuery选择器之基本选择器用法实例分析
2019/02/19 jQuery
基于react项目打包css引用路径错误解决方案
2020/10/28 Javascript
利用JavaScript模拟京东按键输入功能
2020/12/01 Javascript
[02:14]DOTA2英雄基础教程 修补匠
2013/12/23 DOTA
[02:02]特效爆炸!DOTA2珍宝之瓶待你开启
2018/08/21 DOTA
对python添加模块路径的三种方法总结
2018/10/16 Python
python实现停车管理系统
2018/11/30 Python
Python-jenkins 获取job构建信息方式
2020/05/12 Python
python 爬取百度文库并下载(免费文章限定)
2020/12/04 Python
Python 2.6.6升级到Python2.7.15的详细步骤
2020/12/14 Python
GNC健安喜美国官网:美国第一营养品牌
2016/07/22 全球购物
高尔夫球鞋、服装、手套和装备:FootJoy
2018/12/15 全球购物
Proenza Schouler官方网站:纽约女装和配饰品牌
2019/01/03 全球购物
营销总经理的岗位职责
2013/12/15 职场文书
大学社团活动总结
2014/04/26 职场文书
领导班子四风对照检查材料
2014/09/23 职场文书
525心理健康活动总结
2015/05/08 职场文书
实习报告范文之电话客服岗位
2019/07/26 职场文书
python中requests库+xpath+lxml简单使用
2021/04/29 Python
Python - 10行代码集2000张美女图
2021/05/23 Python