首页 > 解决方案 > Python 3 中的代理类

问题描述

我在 python3 中编写了一个简单的代理类,但我遇到了“was_call”函数的问题

class Proxy:
    last_invoked = ""
    calls = {}

    def __init__(self, obj):
        self._obj = obj

    def __getattr__(self, item):
        attrs = dir(self._obj)

        if item in attrs:
            Proxy.last_invoked = item
            if item in Proxy.calls.keys():
                Proxy.calls[item] += 1
            else:
                Proxy.calls[item] = 1
            if item in Proxy.calls.keys():
                Proxy.calls[item] += 1
            else:
                Proxy.calls[item] = 1
            return getattr(self._obj, item)
        else:
            raise Exception('No Such Method')

    def last_invoked_method(self):
        if Proxy.last_invoked == "":
            raise Exception('No Method Is Invoked')
        else:
            return Proxy.last_invoked

    def count_of_calls(self, method_name):
        if method_name in Proxy.calls.keys():
            return Proxy.calls[method_name]
        return 0

    def was_called(self, method_name):
        if method_name in Proxy.calls.keys():
            if Proxy.calls[method_name] > 0: return True
        return False

class Radio():
    def __init__(self):
        self._channel = None
        self.is_on = False
        self.volume = 0

    def get_channel(self):
        return self._channel

    def set_channel(self, value):
        self._channel = value

    def power(self):
        self.is_on = not self.is_on

radio = Radio()
radio_proxy = Proxy(radio)
radio.number = 3
radio_proxy.number = 3
radio_proxy.power()
print(radio_proxy.was_called("number"))
print(radio_proxy.was_called("power"))

“was_call”函数适用于最初在收音机中的函数和属性,例如“power”,但不适用于我们添加的新属性,例如“number”。

我希望两者都打印“True”,因为“power”和“number”都被调用。但首先打印返回 False!

你有什么建议?

标签: python

解决方案


def Proxy(class_type):
    class ProxyClass(class_type):

        def __init__(self, *args, **kwargs):

            # Set your _calls and _last_invoked here, so that they are not class attributes (and are instead instance attributes).
            self._calls = {}
            self._last_invoked = ""

            # Pass the arguments back to the class_type (in our case Radio) to initialize the class.
            super().__init__(*args, **kwargs)

        def __getattribute__(self, item):

            # We must do this prelimary check before continuing on to the elif statement.
            # This is since _calls and _last_invoked is grabbed when self._last_invoked/self._calls is called below.
            if item in ("_calls", "_last_invoked"):
                return super(ProxyClass, self).__getattribute__(item)
            elif not item.startswith("_"):
                self._last_invoked = item
                self._calls[item] = 1 if item not in self._calls.keys() else self._calls[item] + 1

            return super(ProxyClass, self).__getattribute__(item)

        def __setattr__(self, item, val):

            # Wait until _calls is initialized before trying to set anything.
            # Only set items that do not start with _
            if not item == "_calls" and not item.startswith("_"):
                self._calls[item] = 0

            super(ProxyClass, self).__setattr__(item, val)

        def last_invoked_method(self):    
            if self._last_invoked == "":
                raise Exception('No Method Is Invoked')
            else:
                return self._last_invoked

        def count_of_calls(self, method_name):
            return self._calls[method_name] if method_name in self._calls.keys() else 0

        def was_called(self, method_name):
            return True if method_name in self._calls.keys() and self._calls[method_name] > 0 else False

    return ProxyClass

@Proxy
class Radio():
    def __init__(self):
        self._channel = None
        self.is_on = False
        self.volume = 0

    def get_channel(self):
        return self._channel

    def set_channel(self, value):
        self._channel = value

    def power(self):
        self.is_on = not self.is_on
radio = Proxy(Radio)()
radio.number = 3       # Notice that we are only setting the digit here.
radio.power()
print(radio._calls)
print(radio.number)    # Notice that this when we are actually calling it.
print(radio._calls)

输出:

{'is_on': 0, 'volume': 0, 'number': 0, 'power': 1}
3
{'is_on': 0, 'volume': 0, 'number': 1, 'power': 1}

在这里和那里进行了一些修改,但是您应该能够通过阅读代码来看到更大的想法。从这里您应该能够根据自己的喜好修改代码。另请注意,任何以开头的变量_都会自动从_calls字典中删除。

如果您不想使用装饰器@Proxy,您可以像这样初始化您的Radio类(作为代理):

# Second parentheses is where your Radio args go in.
# Since Radio does not take any args, we leave it empty.
radio_proxy = Proxy(Radio)()

另外,请务必了解类属性和实例属性之间的区别。


编辑:

class Test:
    def __init__(self, var):
        self.var = var
        self.dictionary = {}

    def __getattribute__(self, item):
        print("we are GETTING the following item:", item)

        # If we don't do this, you end up in an infinite loop in which Python is
        # trying to get the `dictionary` class to do `self.dictionary['dictionary'] = ...`

        if item == "dictionary":
            super(Test, self).__getattribute__(item)
        else:
            self.dictionary[item] = "Now we can use this!"

        return super(Test, self).__getattribute__(item)

    def __setattr__(self, item, key):
        print("we are SETTING the following item:", item)
        super(Test, self).__setattr__(item, key)

注意:

test = Test(4)

输出:

we are SETTING the following item: var
we are SETTING the following item: dictionary

然后跟随它:

test.var

输出:

we are GETTING the following item: var
we are GETTING the following item: dictionary

推荐阅读