__getattr__ для статических/классовых переменных в python

У меня есть класс вроде:

class MyClass:
     Foo = 1
     Bar = 2

Всякий раз, когда вызывается MyClass.Foo или MyClass.Bar, мне нужно, чтобы пользовательский метод вызывался до того, как значение будет возвращено. Возможно ли это в Python? Я знаю, что это возможно, если я создам экземпляр класса и смогу определить свой собственный метод __getattr__. Но мой сценарий предполагает использование этого класса как такового без создания его экземпляра.

Также мне нужен пользовательский метод __str__, который будет вызываться при вызове str(MyClass.Foo). Предоставляет ли Python такую ​​возможность?


person Tuxdude    schedule 01.07.2010    source источник


Ответы (5)


__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

# in python 3:
# class MyClass(metaclass=FooType):
#    pass


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

печать:

foo!
bar!
custom str for MyClass

И нет, объект не может перехватить запрос на преобразование в строку одного из его атрибутов. Объект, возвращаемый для атрибута, должен определять свое собственное __str__() поведение.

person Matt Anderson    schedule 01.07.2010
comment
Спасибо, Игнасио и Мэтт. Я думаю, что теперь я понимаю, как работает создание класса (не экземпляра), а также я думал, что требуемое поведение str было трудно достичь. Я думаю, что метаклассы должны быть достаточно хороши для моего требования. :) - person Tuxdude; 01.07.2010
comment
Если вы всегда получаете AttributeError, проверьте другой ответ, который имеет синтаксис для python3 (этот пример кажется python2) - person Chris; 21.06.2017

(Я знаю, что это старый вопрос, но поскольку все остальные ответы используют метакласс...)

Вы можете использовать следующий простой дескриптор 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
person shx2    schedule 29.03.2014
comment
Это поможет только в том случае, если вы заранее знаете имена своих свойств. Если вы читаете их из файла и вам нужно реагировать динамически, это не сработает. - person Chris; 21.06.2017

Во-первых, вам нужно создать метакласс и определить для него __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.

person Ignacio Vazquez-Abrams    schedule 01.07.2010
comment
Протестировал этот пример, и он работает только для меня в Python 2.x. После изменения печати на функцию Python 3.2 выдает мне ошибку AttributeError: тип объекта «MyClass» не имеет атрибута «Foo». Какие-либо предложения? - person CyberFonic; 17.11.2011
comment
@CyberED: docs.python.org/py3k/reference/datamodel.html#metaclasses : при чтении определения класса, если аргумент ключевого слова callable metaclass передается после баз в определении класса, указанный callable будет вызываться вместо type(). - person Ignacio Vazquez-Abrams; 17.11.2011
comment
Не мог дождаться .... после небольшого поиска в Google и тестирования: пример для Python 2.x. В Python 3.x вам нужно: класс MyClass (метакласс = MyMetaclass): передать - person CyberFonic; 17.11.2011

Удивлен, что никто не указал на это:

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, справедливо для этого решения: оно должно быть реализовано для фактически возвращаемого типа.

person Jonas Schäfer    schedule 28.01.2014
comment
Хотя обычно я стараюсь воздерживаться от публикации «Спасибо», мне действительно нужно опубликовать «Спасибо» прямо сейчас: Спасибо! - person Chris; 21.06.2017

В зависимости от случая я использую этот шаблон

class _TheRealClass:
    def __getattr__(self, attr):
       pass

LooksLikeAClass = _TheRealClass()

Затем вы импортируете и используете его.

from foo import LooksLikeAClass
LooksLikeAClass.some_attribute

Это позволяет избежать использования метакласса и обрабатывать некоторые варианты использования.

person geckos    schedule 21.09.2018