Python : 从方法内部设置方法属性

Python : Set method attribute from within method

我正在尝试制作一个 python 装饰器,将属性添加到 class 的方法中,以便我可以从方法本身内部访问和修改这些属性。装饰器代码是

from types import MethodType
class attribute(object):

    def __init__(self, **attributes):
        self.attributes = attributes

    def __call__(self, function):

        class override(object):

            def __init__(self, function, attributes):
                self.__function = function
                for att in attributes:
                    setattr(self, att, attributes[att])

            def __call__(self, *args, **kwargs):
                return self.__function(*args, **kwargs)

            def __get__(self, instance, owner):
                return MethodType(self, instance, owner) 

        retval = override(function, self.attributes)
        return retval

我在下面的玩具示例中尝试了这个装饰器。

    class bar(object):

        @attribute(a=2)
        def foo(self):
            print self.foo.a
            self.foo.a = 1

虽然我可以从 foo() 中访问属性 'a' 的值,但我无法将它设置为另一个值。 确实,当我调用 bar().foo() 时,我得到以下 AttributeError。

AttributeError: 'instancemethod' object has no attribute 'a'

这是为什么?更重要的是我怎样才能实现我的目标?


编辑

更具体地说,我试图找到一种简单的方法来实现位于 class 方法中的静态变量。继续上面的示例,我想实例化 b = bar(),同时调用 foo()doo() 方法,然后稍后访问 b.foo.ab.doo.a

    class bar(object):

        @attribute(a=2)
        def foo(self):
            self.foo.a = 1

        @attribute(a=4)
        def doo(self):
            self.foo.a = 3

虽然您可以通过将 self.foo.a = 1 替换为 self.foo.__dict__['a'] = 1 来实现您的目标,但通常不推荐这样做。

如果您正在使用 Python2 -(而不是 Python3)- 每当您从实例中检索方法时,都会创建一个新的 instance method 对象,它是class 主体中定义的原始函数。

实例方法是函数的一个相当透明的代理 - 您可以通过它检索函数的属性,但不能设置它们 - 这就是为什么在 self.foo.__dict__ 中设置项目有效。

或者,您可以使用以下方法访问函数对象本身:self.foo.im_func - 实例方法的 im_func 属性指向基础函数。

最好的方法是根本不做

首先,不需要属性装饰器;你可以自己分配它:

class bar(object):
    def foo(self):
        print self.foo.a
        self.foo.a = 1
    foo.a = 2

但是,这样还是遇到同样的错误。你需要做的:

self.foo.__dict__['a'] = 1

您可以改用元数据class...但这很快就会变得混乱。

另一方面,还有更清洁的替代品。

您可以使用默认值:

def foo(self, a):
    print a[0]
    a[0] = 2
foo.func_defaults = foo.func_defaults[:-1] + ([2],)

当然,我的首选方法是完全避免这种情况并使用可调用的 class(C++ 中的 "functor"):

class bar(object):
    def __init__(self):
        self.foo = self.foo_method(self)
    class foo_method(object):
        def __init__(self, bar):
            self.bar = bar
            self.a = 2
        def __call__(self):
            print self.a
            self.a = 1

或者只使用classic class属性:

class bar(object):
    def __init__(self):
        self.a = 1
    def foo(self):
        print self.a
        self.a = 2

如果您想从派生的 classes 中隐藏 a,请使用 Python 术语中称为的任何私有属性:

class bar(object):
    def __init__(self):
        self.__a = 1 # this will be implicitly mangled as __bar__a or similar
    def foo(self):
        print self.__a
        self.__a = 2

编辑:你想要静态属性吗?

class bar(object):
    a = 1
    def foo(self):
        print self.a
        self.a = 2

编辑 2: 如果您希望静态属性仅对当前函数可见,您可以使用 PyExtmodify_function :

import pyext

def wrap_mod(*args, **kw):
    def inner(f):
        return pyext.modify_function(f, *args, **kw)
    return inner

class bar(object):
    @wrap_mod(globals={'a': [1]})
    def foo(self):
        print a[0]
        a[0] = 2

它有点丑陋和粗糙。但它有效。

我的建议是只使用双下划线:

class bar(object):
    __a = 1
    def foo(self):
        print self.__a
        self.__a = 2

虽然这个对其他函数可见的,但它对其他任何东西都是不可见的(实际上,它就在那里,但被破坏了)。

最终编辑: 使用:

import pyext

def wrap_mod(*args, **kw):
    def inner(f):
        return pyext.modify_function(f, *args, **kw)
    return inner

class bar(object):
    @wrap_mod(globals={'a': [1]})
    def foo(self):
        print a[0]
        a[0] = 2
    foo.a = foo.func_globals['a']

b = bar()
b.foo() # prints 1
b.foo() # prints 2
# external access
b.foo.a[0] = 77
b.foo() # prints 77

根据其他贡献者的回答,我提出了以下解决方法。首先,将字典包装在 class 中,将不存在的属性解析为包装的字典,例如以下代码。

class DictWrapper(object):
    def __init__(self, d):
        self.d = d
    def __getattr__(self, key):
        return self.d[key]

此代码归功于 Lucas Jones

然后使用 statics 属性实现 addstatic 装饰器,该装饰器将存储 static 属性。

class addstatic(object):

    def __init__(self, **statics):
        self.statics = statics

    def __call__(self, function):

        class override(object):

            def __init__(self, function, statics):
                self.__function = function
                self.statics = DictWrapper(statics)

            def __call__(self, *args, **kwargs):
                return self.__function(*args, **kwargs)

            def __get__(self, instance, objtype):
                from types import MethodType
                return MethodType(self, instance)

        retval = override(function, self.statics)
        return retval

以下代码是如何在方法上使用 addstatic 装饰器的示例。

   class bar(object):

        @attribute(a=2, b=3)
        def foo(self):
            self.foo.statics.a = 1
            self.foo.statics.b = 2

然后,使用 bar class 的实例会产生:

>>> b = bar()
>>> b.foo.statics.a
2
>>> b.foo.statics.b
3
>>> b.foo()
>>> b.foo.statics.a
3
>>> b.foo.statics.b
5

使用这个静态词典的原因遵循 jsbueno 的回答,这表明我想要的是需要重载点运算符和包装 foo 函数的实例方法,我不确定这是可能的。当然,该方法的属性可以在 self.foo.__dict__ 中设置,但由于不推荐(如 brainovergrow 所建议的),我想出了这个解决方法。我不确定这是否会被推荐,我想它正在征求意见。