简体   繁体   中英

python global decorator for class attributes

Below is an abstracted piece of code that simplifies an issue that I have. In this example, I have a program that has a login and logout attributes. The login is version-independent and logout is version-dependent.

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 is an external interface that performs the actual action, and it should receive a string. The do function cannot be altered. The version can and will change at run-time, so I was looking for some sort of global decorator/property that will call a function when the decorated attribute is accessed. The objective is to select the correct string per version before it is sent to executor.do .

The obvious answer is to change the perform function to executer.do(self.logout[self.version]) , but self.login and self.logout should not be accessed differently. There are inheritances in which self.logout is only a string, and perform is shared.

I was thinking of something like:

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]

That obviously doesn't work. Is this even possible?

Manual Solution

Looks like a use case for a property decorator:

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]

Now:

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

Automated Solution 1

If you have lot such properties, you can automate a bit:

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]))

Now:

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

Automated Solution 2

The "Automated Solution 1" redefines the properties every time you make a new instance of A . This solutions avoids this but is bit more involved. It makes use of a class decorator.

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'

Now:

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

You say that " self.login and self.logout should not be accessed differently. The code below keeps the self.logout dictionary but renames it to self.logouts so that we can access it as a property. Similar remarks apply to self.logout_message .

This code runs on Python 2 or 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)

output

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'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM