静态/类变量的__getattr__方法

79

我有一个类,如下:

class MyClass:
     Foo = 1
     Bar = 2

每当调用MyClass.FooMyClass.Bar时,我需要在返回值之前调用自定义方法。在Python中是否可能?我知道如果我创建类的实例并且可以定义自己的__getattr__方法,那么这是可能的。但我的场景涉及在不创建任何实例的情况下使用此类。
另外,当调用str(MyClass.Foo)时,我需要调用自定义__str__方法。Python是否提供了这样的选项?
5个回答

99

__getattr__()__str__()方法在对象的类中定义,如果你想要自定义一个类的这些内容,你需要使用元类(即类的类)。

class FooType(type):
    def _foo_func(cls):
        return 'foo!'

    def _bar_func(cls):
        return 'bar!'

    def __getattr__(cls, key):
        if key == 'Foo':
            return cls._foo_func()
        elif key == 'Bar':
            return cls._bar_func()
        raise AttributeError(key)

    def __str__(cls):
        return 'custom str for %s' % (cls.__name__,)

class MyClass(metaclass=FooType):
    pass

# # in python 2:
# class MyClass:
#    __metaclass__ = FooType


print(MyClass.Foo)
print(MyClass.Bar)
print(str(MyClass))

打印:

foo!
bar!
custom str for MyClass

不,一个对象不能截获对其属性进行字符串转换的请求。返回该属性的对象必须定义自己的__str__()行为。

更新 2023-02-20,适用于Python 3.x默认实现(python 2以注释形式提供)。


感谢Ignacio和Matt。我现在明白了类(而不是实例)的创建方式,也认为所需的__str__行为很难实现。我认为元类应该足以满足我的要求。 :) - Tuxdude
1
如果你总是得到 AttributeError,请查看其他答案,其中包含Python3的语法(此示例似乎是Python2)。 - Chris
@Chris:这段代码的基本思路在Python 3中可以工作。除了print语句之外,唯一需要更改的是元类的指定方式(如现在所述的评论中)。 - martineau
面板认为将此答案的编辑以Python 3语法为主,并将Python 2降至评论中,这样做是否可行?我很乐意进行编辑。 - Martin Bonner supports Monica
@MartinBonnersupportsMonica 已完成。我有很多帖子可能需要更新。13年前的Python与现在非常不同。 - Matt Anderson

22

我知道这是一个旧问题,但由于所有其他答案都使用元类,因此...

您可以使用以下简单的classproperty描述符:

class classproperty(object):
    """ @classmethod+@property """
    def __init__(self, f):
        self.f = classmethod(f)
    def __get__(self, *a):
        return self.f.__get__(*a)()

使用方法:

class MyClass(object):

     @classproperty
     def Foo(cls):
        do_something()
        return 1

     @classproperty
     def Bar(cls):
        do_something_else()
        return 2

4
只有当您事先知道属性的名称时,它才有帮助。如果您需要从文件中读取并需要动态响应,则这种方法是行不通的。 - Chris
1
理论上它仍然可以工作——从文件中读取它们,并在for循环中调用setattr(MyClass, prop_name, classproperty(..., ...)) - ntjess

12

首先,你需要创建一个元类,然后在其中定义__getattr__() 方法。

class MyMetaclass(type):
  def __getattr__(self, name):
    return '%s result' % name

class MyClass(object):
  __metaclass__ = MyMetaclass

print MyClass.Foo

对于第二个问题,不可以。调用str(MyClass.Foo)会触发MyClass.Foo.__str__(),因此您需要为MyClass.Foo返回一个适当的类型。


1
测试了这个例子,只有在Python 2.x中才能正常工作。将print更改为函数后,Python 3.2会给出“AttributeError:type object 'MyClass' has no attribute 'Foo'”错误。有什么建议吗? - CyberFonic
2
@CyberED: http://docs.python.org/py3k/reference/datamodel.html#metaclasses :“当类定义被读取时,如果在类定义中的基类之后传递了一个可调用的metaclass关键字参数,则将调用给定的可调用对象,而不是type()。” - Ignacio Vazquez-Abrams
4
例子是针对 Python 2.x 版本的。在 Python 3.x 中,你需要这样写:class MyClass(metaclass=MyMetaclass): pass - CyberFonic

9

我很惊讶没有人指出这一点:

class FooType(type):
    @property
    def Foo(cls):
        return "foo!"

    @property
    def Bar(cls):
        return "bar!"

class MyClass(metaclass=FooType):
    pass

工作内容:

>>> MyClass.Foo
'foo!'
>>> MyClass.Bar
'bar!'

如果是Python 2.x版本,请将MyClass的定义修改为:

class MyClass(object):
    __metaclass__ = FooType

其他答案对于str的解释同样适用于这个解决方案:它必须在实际返回的类型上实现。

2
虽然我通常试图避免发布“感谢”,但我现在确实需要发表“感谢”:谢谢! - Chris
如在其他类似属性回答的评论中提到的,只有在预先知道属性名称的情况下,这才是可行的。 - martineau

1
根据情况,我使用这个模式。
class _TheRealClass:
    def __getattr__(self, attr):
       pass

LooksLikeAClass = _TheRealClass()

然后您导入并使用它。
from foo import LooksLikeAClass
LooksLikeAClass.some_attribute

这样可以避免使用元类,并处理一些使用情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接