Как создать свойство класса?

В python я могу добавить метод в класс с @classmethod декоратора @classmethod . Есть ли аналогичный декоратор для добавления свойства в класс? Я могу лучше показать, о чем я говорю.

 class Example(object): the_I = 10 def __init__( self ): self.an_i = 20 @property def i( self ): return self.an_i def inc_i( self ): self.an_i += 1 # is this even possible? @classproperty def I( cls ): return cls.the_I @classmethod def inc_I( cls ): cls.the_I += 1 e = Example() assert ei == 20 e.inc_i() assert ei == 21 assert Example.I == 10 Example.inc_I() assert Example.I == 11 

Является ли синтаксис, который я использовал выше, или потребовал бы чего-то большего?

Причина, по которой я хочу свойства класса, – это то, что я могу использовать ленивые атрибуты класса загрузки, которые кажутся достаточно разумными.

6 Solutions collect form web for “Как создать свойство класса?”

Вот как бы я это сделал:

 class ClassPropertyDescriptor(object): def __init__(self, fget, fset=None): self.fget = fget self.fset = fset def __get__(self, obj, klass=None): if klass is None: klass = type(obj) return self.fget.__get__(obj, klass)() def __set__(self, obj, value): if not self.fset: raise AttributeError("can't set attribute") type_ = type(obj) return self.fset.__get__(obj, type_)(value) def setter(self, func): if not isinstance(func, (classmethod, staticmethod)): func = classmethod(func) self.fset = func return self def classproperty(func): if not isinstance(func, (classmethod, staticmethod)): func = classmethod(func) return ClassPropertyDescriptor(func) class Bar(object): _bar = 1 @classproperty def bar(cls): return cls._bar @bar.setter def bar(cls, value): cls._bar = value # test instance instantiation foo = Bar() assert foo.bar == 1 baz = Bar() assert baz.bar == 1 # test static variable baz.bar = 5 assert foo.bar == 5 # test setting variable on the class Bar.bar = 50 assert baz.bar == 50 assert foo.bar == 50 

сеттер не работал в то время, когда мы называем «Bar.bar», потому что мы вызываем « TypeOfBar.bar .__ set__ », который не является « Bar.bar .__ set__ », добавьте определение метакласса для его решения

 class ClassPropertyMetaClass(type): def __setattr__(self, key, value): if key in self.__dict__: obj = self.__dict__.get(key) if obj and type(obj) is ClassPropertyDescriptor: return obj.__set__(self, value) return super(ClassPropertyMetaClass, self).__setattr__(key, value) # and update class define: # class Bar(object): # __metaclass__ = ClassPropertyMetaClass # _bar = 1 # and update ClassPropertyDescriptor.__set__ # def __set__(self, obj, value): # if not self.fset: # raise AttributeError("can't set attribute") # if inspect.isclass(obj): # type_ = obj # obj = None # else: # type_ = type(obj) # return self.fset.__get__(obj, type_)(value) 

теперь будет хорошо

Если вы определяете classproperty следующим образом, то ваш пример работает точно так же, как вы просили.

 class classproperty(object): def __init__(self, f): self.f = f def __get__(self, obj, owner): return self.f(owner) 

Предостережение заключается в том, что вы не можете использовать это для доступных для записи свойств. В то время как eI = 20 повысит значение AttributeError , Example.I = 20 перезапишет сам объект свойства.

Я думаю, что вы можете сделать это с помощью метакласса. Поскольку метакласс может быть как класс для класса (если это имеет смысл). Я знаю, что вы можете назначить __call__() для метакласса, чтобы переопределить вызов класса MyClass() . Интересно, работает ли аналогично с помощью декоратора property в метаклассе. (Я раньше этого не пробовал, но теперь мне любопытно …)

[Обновить:]

Вау, он работает:

 class MetaClass(type): def getfoo(self): return self._foo foo = property(getfoo) @property def bar(self): return self._bar class MyClass(object): __metaclass__ = MetaClass _foo = 'abc' _bar = 'def' print MyClass.foo print MyClass.bar 

Примечание: это в Python 2.7. Python 3+ использует другую технику для объявления метакласса. Используйте: class MyClass(metaclass=MetaClass): удалите __metaclass__ , а остальное то же самое.

[ответ написан на основе python 3.4; синтаксис метакласса отличается в 2, но я думаю, что техника все равно будет работать]

Вы можете сделать это с помощью метакласса … в основном. Даппавит почти работает, но я думаю, что у него есть недостаток:

 class MetaFoo(type): @property def thingy(cls): return cls._thingy class Foo(object, metaclass=MetaFoo): _thingy = 23 

Это дает вам классное свойство Foo, но есть проблема …

 print("Foo.thingy is {}".format(Foo.thingy)) # Foo.thingy is 23 # Yay, the classmethod-property is working as intended! foo = Foo() if hasattr(foo, "thingy"): print("Foo().thingy is {}".format(foo.thingy)) else: print("Foo instance has no attribute 'thingy'") # Foo instance has no attribute 'thingy' # Wha....? 

Что, черт возьми, здесь происходит? Почему я не могу получить свойство класса из экземпляра?

Я довольно долго бил головой об этом, прежде чем найти то, что я считаю ответом. Python @properties – это подмножество дескрипторов, и из документации дескриптора (основное внимание):

Поведение по умолчанию для доступа к атрибутам – это получение, установка или удаление атрибута из словаря объекта. Например, ax имеет цепочку поиска, начинающуюся с a.__dict__['x'] , затем type(a).__dict__['x'] и продолжите через базовые классы type(a) исключая метаклассы .

Таким образом, порядок разрешения метода не включает наши свойства класса (или что-либо еще, определенное в метаклассе). Можно сделать подкласс встроенного декоратора свойств, который ведет себя по-другому, но (цитата). У меня есть впечатление, что у разработчиков есть веская причина (что я не понимаю) для этого.

Это не значит, что нам не повезло; мы можем получить доступ к свойствам самого класса просто … и мы можем получить класс от type(self) внутри экземпляра, который мы можем использовать для создания диспетчеров @property:

 class Foo(object, metaclass=MetaFoo): _thingy = 23 @property def thingy(self): return type(self).thingy 

Теперь Foo().thingy работает как для класса, так и для экземпляров! Он также будет продолжать делать правильные вещи, если производный класс заменяет его базовый _thingy (который является прецедентом, который _thingy меня на этой охоте изначально).

Это не на 100% удовлетворяет мне – делать настройку как в метаклассе, так и в классе объектов, похоже, нарушает принцип DRY. Но последний – это просто диспетчер с одной строкой; Я в основном схожу с этим, и вы, вероятно, могли бы сжать его до лямбды или что-то, если вы действительно этого хотели.

Если вам нужна только ленивая загрузка, тогда у вас может быть только метод инициализации класса.

 EXAMPLE_SET = False class Example(object): @classmethod def initclass(cls): global EXAMPLE_SET if EXAMPLE_SET: return cls.the_I = 'ok' EXAMPLE_SET = True def __init__( self ): Example.initclass() self.an_i = 20 try: print Example.the_I except AttributeError: print 'ok class not "loaded"' foo = Example() print foo.the_I print Example.the_I 

Но метаклассовый подход кажется более чистым и более предсказуемым.

Возможно, именно то, что вы ищете, это шаблон дизайна Singleton . Там есть хороший SO Q о реализации общего состояния в Python.

Насколько я могу судить, нет способа написать сеттер для свойства класса без создания нового метакласса.

Я обнаружил, что работает следующий метод. Определите метакласс со всеми свойствами и настройками класса, которые вы хотите. IE, мне нужен класс с свойством title с установщиком. Вот что я написал:

 class TitleMeta(type): @property def title(self): return getattr(self, '_title', 'Default Title') @title.setter def title(self, title): self._title = title # Do whatever else you want when the title is set... 

Теперь сделайте фактический класс, который вы хотите, как обычно, за исключением того, что он использует метакласс, который вы создали выше.

 # Python 2 style: class ClassWithTitle(object): __metaclass__ = TitleMeta # The rest of your class definition... # Python 3 style: class ClassWithTitle(object, metaclass = TitleMeta): # Your class definition... 

Немного странно определять этот метакласс, как мы делали выше, если мы будем использовать его только в одном классе. В этом случае, если вы используете стиль Python 2, вы можете определить метакласс внутри тела класса. Таким образом, он не определен в области модуля.

 
Interesting Posts for Van-Lav

Как загрузить пользовательский JS-файл в Django admin домой?

tornado отображает GET и POST аргументы в списки. Как отключить эту функцию?

Что делать, если я не закрываю соединение с базой данных в Python SQLite

setup.py не устанавливать файлы данных

Начать nano как подпроцесс из python, захватить ввод

Использование sqlalchemy для запроса с использованием нескольких столбцов, где в разделе

Как создать колесо из приложения django?

Получить смещение текущего буфера в vim (в частности, с помощью скриптов python)

Сохранить аудиофайлы KIVY, PYTHON

Есть ли какая-то тщательная обширная документация Twisted, которая лучше официального сайта?

Как область круговой маркерной шкалы Matplotlib с ее радиусом?

Повторная строка Python

RabbitMQ потребляет одно сообщение, если оно существует, и завершает работу

Django: как скрыть / перезаписать метку по умолчанию с помощью ModelForm?

Google App Engine добавляет изображение на страницу со статической папкой

Python - лучший язык программирования в мире.