首頁 > 軟體

Python的代理類實現,控制存取和修改屬性的許可權你都瞭解嗎

2022-03-21 19:01:06

本篇文章主要內容

代理類主要功能是將一個類範例的屬性存取和控制代理到程式碼內部另外一個範例類,將想對外公佈的屬性的存取和控制權交給代理類來操作,保留不想對外公佈的屬性的存取或控制權,比如唯讀存取,紀錄檔功能

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雙下劃線開頭和結尾的方法,如果想要被代理類存取和控制,就必須在代理類中也定義對應的實際方法,另外,範例中主要是以下劃線開頭的方法作為私有屬性的約定,也可以使用其他約定,這樣在代理方法中的存取和修改時做出相應的判斷即可

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容! 


IT145.com E-mail:sddin#qq.com