2013-07-28 4 views
41

Python 3.4 представляет новый модуль enum, который добавляет enumerated type к этому языку. Документация enum.Enum обеспечивает an example, чтобы продемонстрировать, как он может быть продлен:Можно ли определить константу класса внутри Enum?

>>> class Planet(Enum): 
...  MERCURY = (3.303e+23, 2.4397e6) 
...  VENUS = (4.869e+24, 6.0518e6) 
...  EARTH = (5.976e+24, 6.37814e6) 
...  MARS = (6.421e+23, 3.3972e6) 
...  JUPITER = (1.9e+27, 7.1492e7) 
...  SATURN = (5.688e+26, 6.0268e7) 
...  URANUS = (8.686e+25, 2.5559e7) 
...  NEPTUNE = (1.024e+26, 2.4746e7) 
...  def __init__(self, mass, radius): 
...   self.mass = mass  # in kilograms 
...   self.radius = radius # in meters 
...  @property 
...  def surface_gravity(self): 
...   # universal gravitational constant (m3 kg-1 s-2) 
...   G = 6.67300E-11 
...   return G * self.mass/(self.radius * self.radius) 
... 
>>> Planet.EARTH.value 
(5.976e+24, 6378140.0) 
>>> Planet.EARTH.surface_gravity 
9.802652743337129 

Этот пример также демонстрирует проблемы с Enum: в методе surface_gravity() собственности, постоянная G определяется, который, как правило, определенный на уровне класса - но попытка сделать это внутри Enum просто добавит его как один из членов перечисления, поэтому вместо этого он определен внутри метода.

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

Есть ли способ определить константу класса внутри Enum или какое-либо обходное решение для достижения такого же эффекта?

+4

В чем проблема с константой на уровне модуля? – delnan

+2

@ delnan иногда константа специфична для класса. –

+0

Я знаю, что пример из официальных документов, но действительно ли это хорошая идея? Это может быть отношение 'has-a' вместо' is-a'. –

ответ

35

Это расширенное поведение, которое не понадобится в 90 +% от созданных перечислений.

Согласно документации:

The rules for what is allowed are as follows: _sunder_ names (starting and ending with a single underscore) are reserved by enum and cannot be used; all other attributes defined within an enumeration will become members of this enumeration, with the exception of __dunder__ names and descriptors (methods are also descriptors).

Так что, если вы хотите постоянный класс у вас есть несколько вариантов:

  • создать в __init__
  • добавить его после того, как класс был создан
  • использовать миксин
  • создать собственный descriptor

Создание константы в __init__ и добавление ее после создания класса страдают от отсутствия всей информации о классе, собранной в одном месте.

Примеси, безусловно, могут быть использованы при необходимости (see dnozay's answer for a good example), но этот случай также может быть упрощена путем иметь базовый Enum класс с фактическими констант, построенных в.

Во-первых, константа, которая будет использоваться в примерах ниже:

class Constant: # use Constant(object) if in Python 2 
    def __init__(self, value): 
     self.value = value 
    def __get__(self, *args): 
     return self.value 
    def __repr__(self): 
     return '%s(%r)' % (self.__class__.__name__, self.value) 

И одноразового Enum Например:

from enum import Enum 

class Planet(Enum): 
    MERCURY = (3.303e+23, 2.4397e6) 
    VENUS = (4.869e+24, 6.0518e6) 
    EARTH = (5.976e+24, 6.37814e6) 
    MARS = (6.421e+23, 3.3972e6) 
    JUPITER = (1.9e+27, 7.1492e7) 
    SATURN = (5.688e+26, 6.0268e7) 
    URANUS = (8.686e+25, 2.5559e7) 
    NEPTUNE = (1.024e+26, 2.4746e7) 

    # universal gravitational constant 
    G = Constant(6.67300E-11) 

    def __init__(self, mass, radius): 
     self.mass = mass  # in kilograms 
     self.radius = radius # in meters 
    @property 
    def surface_gravity(self): 
     return self.G * self.mass/(self.radius * self.radius) 

print(Planet.__dict__['G'])    # Constant(6.673e-11) 
print(Planet.G)       # 6.673e-11 
print(Planet.NEPTUNE.G)     # 6.673e-11 
print(Planet.SATURN.surface_gravity) # 10.44978014597121 

И, наконец, многофункциональный Enum пример:

from enum import Enum 

class AstronomicalObject(Enum): 

    # universal gravitational constant 
    G = Constant(6.67300E-11) 

    def __init__(self, mass, radius): 
     self.mass = mass 
     self.radius = radius 
    @property 
    def surface_gravity(self): 
     return self.G * self.mass/(self.radius * self.radius) 

class Planet(AstronomicalObject): 
    MERCURY = (3.303e+23, 2.4397e6) 
    VENUS = (4.869e+24, 6.0518e6) 
    EARTH = (5.976e+24, 6.37814e6) 
    MARS = (6.421e+23, 3.3972e6) 
    JUPITER = (1.9e+27, 7.1492e7) 
    SATURN = (5.688e+26, 6.0268e7) 
    URANUS = (8.686e+25, 2.5559e7) 
    NEPTUNE = (1.024e+26, 2.4746e7) 

class Asteroid(AstronomicalObject): 
    CERES = (9.4e+20 , 4.75e+5) 
    PALLAS = (2.068e+20, 2.72e+5) 
    JUNOS = (2.82e+19, 2.29e+5) 
    VESTA = (2.632e+20 ,2.62e+5 

Planet.MERCURY.surface_gravity # 3.7030267229659395 
Asteroid.CERES.surface_gravity # 0.27801085872576176 

Примечание:

ConstantG действительно нет. Можно перепривязывают G к чему-то еще:

Planet.G = 1 

Если вам действительно нужно, чтобы быть постоянным (он же не rebindable), а затем использовать new aenum library [1], которая будет блокировать попытки перераспределить constant s, а также Enum членов.


[1] aenum написана автором enum34.

+0

Есть ли преимущество или недостаток вашего класса 'Constant' над функцией' classconstant() ', возвращающей замыкание, которое предложил @AnttiHaapala? Я замечаю, что 'Planet .__ dict __ ['G']' имеет немного более приятную версию в вашей версии; есть ли еще что-нибудь? –

+2

@ZeroPiraeus: Это проще и легче читать, поэтому больше * pythonic *;). Если вы хотите приятный '__repr__', добавьте его. Я только что сделал. –

+0

Хм, действительно, я думаю, что я думал, когда использовал закрытие. –

4

property может быть использован для обеспечения большей части поведения константы класса:

class Planet(Enum): 

    # ... 

    @property 
    def G(self): 
     return 6.67300E-11 

    # ... 

    @property 
    def surface_gravity(self): 
     return self.G * self.mass/(self.radius * self.radius) 

Это будет немного громоздким, если вы хотите, чтобы определить большое количество констант, так что вы могли бы определить помощник функция вне класса:

def constant(c): 
    """Return a class property that returns `c`.""" 
    return property(lambda self: c) 

... и использовать его следующим образом:

class Planet(Enum): 

    # ... 

    G = constant(6.67300E-11) 

Одно ограничение этого подхода заключается в том, что она будет работать только для экземпляров класса, а не сам класс:

>>> Planet.EARTH.G 
6.673e-11 
>>> Planet.G 
<property object at 0x7f665921ce58> 
9
from enum import Enum 


class classproperty(object): 
    """A class property decorator""" 

    def __init__(self, getter): 
     self.getter = getter 

    def __get__(self, instance, owner): 
     return self.getter(owner) 


class classconstant(object): 
    """A constant property from given value, 
     visible in class and instances""" 

    def __init__(self, value): 
     self.value = value 

    def __get__(self, instance, owner): 
     return self.value 


class strictclassconstant(classconstant): 
    """A constant property that is 
     callable only from the class """ 

    def __get__(self, instance, owner): 
     if instance: 
      raise AttributeError(
       "Strict class constants are not available in instances") 

     return self.value 


class Planet(Enum): 
    MERCURY = (3.303e+23, 2.4397e6) 
    VENUS = (4.869e+24, 6.0518e6) 
    EARTH = (5.976e+24, 6.37814e6) 
    MARS = (6.421e+23, 3.3972e6) 
    JUPITER = (1.9e+27, 7.1492e7) 
    SATURN = (5.688e+26, 6.0268e7) 
    URANUS = (8.686e+25, 2.5559e7) 
    NEPTUNE = (1.024e+26, 2.4746e7) 
    def __init__(self, mass, radius): 
     self.mass = mass  # in kilograms 
     self.radius = radius # in meters 

    G = classconstant(6.67300E-11) 

    @property 
    def surface_gravity(self): 
     # universal gravitational constant (m3 kg-1 s-2) 
     return Planet.G * self.mass/(self.radius * self.radius) 


print(Planet.MERCURY.surface_gravity) 
print(Planet.G) 
print(Planet.MERCURY.G) 

class ConstantExample(Enum): 
    HAM = 1 
    SPAM = 2 


    @classproperty 
    def c1(cls): 
     return 1 

    c2 = classconstant(2) 

    c3 = strictclassconstant(3) 

print(ConstantExample.c1, ConstantExample.HAM.c1) 
print(ConstantExample.c2, ConstantExample.SPAM.c2) 
print(ConstantExample.c3) 


# This should fail: 
print(ConstantExample.HAM.c3) 

Причина @property не работает и classconstant ДЕЛАЕТ работа довольно проста, и объяснено в answer here

The reason that the actual property object is returned when you access it via a class Hello.foo lies in how the property implements the __get__(self, instance, owner) special method. If a descriptor is accessed on an instance, then that instance is passed as the appropriate argument, and owner is the class of that instance.

On the other hand, if it is accessed through the class, then instance is None and only owner is passed. The property object recognizes this and returns self.

Таким образом, код в classproperty на самом деле является обобщением property, не имея if instance is None части.

+0

Что-то изменилось с python 2; в python 2 в какой-то момент, если __get__ выбрасывает любой атрибут AttributeError, снаружи он показан просто как «Объект Bar не имеет атрибута foo», но, похоже, на Python 3 больше не работает. –

13

Наиболее элегантным решением (IMHO) является использование mixins/base class для обеспечения правильного поведения.

  • базовый класс, обеспечивающий поведение, которое необходимо для всей реализации, которая является общей для, например, Satellite и Planet.
  • mixins are interesting если вы решили обеспечить дополнительное поведение (например, Satellite и Planet, возможно, придется предоставить другое поведение)

Вот пример, где вы сначала определить свое поведение:

# 
# business as usual, define your class, methods, constants... 
# 
class AstronomicalObject: 
    # universal gravitational constant 
    G = 6.67300E-11 
    def __init__(self, mass, radius): 
     self.mass = mass  # in kilograms 
     self.radius = radius # in meters 

class PlanetModel(AstronomicalObject): 
    @property 
    def surface_gravity(self): 
     return self.G * self.mass/(self.radius * self.radius) 

class SatelliteModel(AstronomicalObject): 
    FUEL_PRICE_PER_KG = 20000 
    @property 
    def fuel_cost(self): 
     return self.FUEL_PRICE_PER_KG * self.mass 
    def falling_rate(self, destination): 
     return complicated_formula(self.G, self.mass, destination) 

Тогда создайте свой Enum с правильными базовыми классами/mixins.

# 
# then create your Enum with the correct model. 
# 
class Planet(PlanetModel, Enum): 
    MERCURY = (3.303e+23, 2.4397e6) 
    VENUS = (4.869e+24, 6.0518e6) 
    EARTH = (5.976e+24, 6.37814e6) 
    MARS = (6.421e+23, 3.3972e6) 
    JUPITER = (1.9e+27, 7.1492e7) 
    SATURN = (5.688e+26, 6.0268e7) 
    URANUS = (8.686e+25, 2.5559e7) 
    NEPTUNE = (1.024e+26, 2.4746e7) 

class Satellite(SatelliteModel, Enum): 
    GPS1 = (12.0, 1.7) 
    GPS2 = (22.0, 1.5) 
+1

У вас есть небольшая ошибка: 'G = Constant (6.67300E-11)' должен быть 'G = 6.67300E-11'. –

+0

@ ZeroPiraeus, исправил его, спасибо, что поймал это. – dnozay

+0

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

0

TLDR; НЕТ, это невозможно сделать внутри класса Enum.

Это сказало, что, как показали другие ответы, существуют способы получить такие принадлежащие классу значения, связанные с Enum (то есть через наследование класса/mixins), но такие значения не «определены .. внутри Enum».

+0

Нет, константы класса обычно определяются на уровне класса, так что они распределяются между всеми экземплярами. –

+0

@ZeroPiraeus Имеет ли значение макет памяти, если они установлены при инициализации? более точным является набор для каждого экземпляра или надутый? Потому что это применимо и к перечислению, не так ли? – ABri

+0

Я не думаю, что комментарии к ответу на вопрос об перечислениях (которые не ведут себя как обычные классы несколькими способами) - это хорошее место, чтобы объяснить, как атрибуты класса и экземпляра работают на Python, если честно. Вы можете найти http://stackoverflow.com/questions/68645/static-class-variables-in-python и https://docs.python.org/2/tutorial/classes.html#class-objects полезным. –