python class 个属性的全局装饰器

python global decorator for class attributes

下面是一段简化了我遇到的问题的抽象代码。 在此示例中,我有一个具有登录和注销属性的程序。 登录与版本无关,注销与版本相关。

class A(class):
    def __init__(self):
        self.version = "1.0"

        self.login = "logged in"
        self.login_message = "hello logger"
        self.logout = {"1.0": "logged out",
                       "2.0": "logged out 2.0"}
        self.logout_message = {"1.0": "goodbye logger",
                               "2.0": "goodbye logger 2.0"}

    def perform(self, executor):
        executor.do(self.login)
        executor.do(self.logout)

executor 是执行实际操作的外部接口,它应该接收一个字符串。 do 函数无法更改。 该版本可以并且将会在 运行 时间发生变化,所以我一直在寻找某种全局 decorator/property ,它会在访问装饰属性时调用一个函数。 objective 是 select 每个版本的正确字符串,然后再发送到 executor.do

显而易见的答案是将 perform 函数更改为 executer.do(self.logout[self.version]),但不应以不同方式访问 self.loginself.logout。有继承,其中 self.logout 只是一个字符串,而 perform 是共享的。

我在想类似的东西:

self.version = "1.0"

self.login = "logged in"        
self.login_message = "hello logger"
@by_version
self.logout = {"1.0": "logged out",
               "2.0": "logged out 2.0"}
@by_version
self.logout_message = {"1.0": "goodbye logger",
                       "2.0": "goodbye logger 2.0"}

def by_version(self, attribute):
    return attribute[self.version]

这显然行不通。 这可能吗?

手动解决

看起来像是 property 装饰器的用例:

class A(object):
    def __init__(self):
        self.version = "1.0"

        self.login = "logged in"
        self.login_message = "hello logger"

    @property    
    def logout(self):
        return {"1.0": "logged out", "2.0": "logged out 2.0"}[self.version]

    @property    
    def logout_message(self):
        return {"1.0": "goodbye logger", "2.0": "goodbye logger 2.0"}[self.version]

现在:

>>> a = A()
>>> a.login
'logged in'
>>> a.logout
'logged out'
>>> a.version = '2.0'
>>> a.logout
'logged out 2.0'     

自动化解决方案 1

如果你有很多这样的属性,你可以自动化一点:

class A(object):
    def __init__(self):
        self.version = '1.0'
        self.login = 'logged in'
        self.login_message = 'hello logger'
        property_attrs = {'logout': {'1.0': 'logged out', 
                                     '2.0': 'logged out 2.0'},
                          'logout_message': {'1.0': 'goodbye logger',
                                             '2.0': 'goodbye logger 2.0'}}
        for name, value in property_attrs.items():
            setattr(self.__class__, name, property(lambda x: value[x.version]))

现在:

>>> a = A()
>>> a.login_message
'hello logger'
>>> a.logout
'goodbye logger'
>>> a.version = '2.0'
>>> a.logout
'goodbye logger 2.0'

自动化解决方案 2

"Automated Solution 1" 每次创建时都会重新定义属性 A 的新实例。该解决方案避免了这种情况,但涉及更多。 它使用 class 装饰器。

property_attrs = {'logout': {'1.0': 'logged out', '2.0': 'logged out 2.0'},
                  'logout_message': {'1.0': 'goodbye logger', '2.0': 'goodbye logger 2.0'}}

def add_properties(property_attrs):
    def decorate(cls):
        for name, value in property_attrs.items():
            setattr(cls, name, property(lambda self: value[self.version]))
        return cls
    return decorate

@add_properties(property_attrs)
class A(object):
    def __init__(self):
        self.version = '1.0'
        self.login = 'logged in'
        self.login_message = 'hello logger'

现在:

>>> a = A()
>>> a.logout
'goodbye logger'
>>> a.version = '2.0'
>>> a.logout
'goodbye logger 2.0'

您说“self.loginself.logout 不应以不同方式访问。下面的代码保留了 self.logout 字典,但将其重命名为 self.logouts 以便我们可以作为 属性 访问它。类似的评论适用于 self.logout_message

此代码在 Python 2 或 3 上运行。

from __future__ import print_function

class Executor(object):
    def do(self, s):
        print('Executing %r' % s)


class A(object):
    def __init__(self, version="1.0"):
        self.version = version

        self.login = "logged in"
        self.login_message = "hello logger"
        self.logouts = {
            "1.0": "logged out",
            "2.0": "logged out 2.0",
        }
        self.logout_messages = {
            "1.0": "goodbye logger",
            "2.0": "goodbye logger 2.0",
        }

    @property
    def logout(self):
        return self.logouts[self.version]

    @property
    def logout_message(self):
        return self.logout_messages[self.version]

    def perform(self, executor):
        executor.do(self.login)
        executor.do(self.logout)

executor = Executor()
executor.do('Tests')

#Test

a = A()
a.perform(executor)
print('msg', a.logout)
a.version = "2.0"
a.perform(executor)
print('msg', a.logout)
print()

b = A("2.0")
b.perform(executor)
print('msg', b.logout)
b.version = "3.0"
b.perform(executor)

输出

Executing 'Tests'
Executing 'logged in'
Executing 'logged out'
msg logged out
Executing 'logged in'
Executing 'logged out 2.0'
msg logged out 2.0

Executing 'logged in'
Executing 'logged out 2.0'
msg logged out 2.0
Executing 'logged in'
Traceback (most recent call last):
  File "./qtest.py", line 69, in <module>
    b.perform(executor)
  File "./qtest.py", line 50, in perform
    executor.do(self.logout)
  File "./qtest.py", line 42, in logout
    return self.logouts[self.version]
KeyError: '3.0'