54

Python 3.4 では、言語に列挙型enumを追加する新しいモジュールが導入されています。のドキュメントには、拡張方法を示す例が示されています。enum.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

5 に答える 5

48

これは高度な動作であり、作成された列挙の 90% 以上では必要ありません。

ドキュメントによると:

許可されるもののルールは次のとおりです。_sunder_名前 (単一のアンダースコアで開始および終了する) は列挙型によって予約されており、使用できません。__dunder__列挙内で定義された他のすべての属性は、名前とdescriptors(メソッドも記述子です)を除いて、この列挙のメンバーになります。

したがって、クラス定数が必要な場合は、いくつかの選択肢があります。

  • で作成します__init__
  • クラスが作成された後に追加します
  • ミックスインを使用する
  • 自分で作るdescriptor

で定数を作成し__init__、クラスが作成された後に追加すると、すべてのクラス情報が 1 か所に集められないという問題が生じます。

適切な場合はミックスインを使用できますが (良い例については dnozay の回答を参照してください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

:

Constant G本当にそうではありません。G別のものに再バインドできます。

Planet.G = 1

本当に定数にする必要がある場合 (つまり、再バインド可能でない場合)、新しい aenum ライブラリ[1] を使用して、メンバーconstantだけでなく s の再割り当ての試みをブロックします。Enum


1開示: 私はPython stdlibEnumenum34backport、およびAdvanced Enumeration ( aenum) ライブラリの作成者です。

于 2013-08-03T16:49:40.837 に答える
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 が機能する理由は非常に単純で、こちらの回答で説明されています

クラス Hello.foo を介してアクセスすると、実際のプロパティ オブジェクトが返される理由は、プロパティが __get__(self, instance, owner)特別なメソッドを実装する方法にあります。記述子がインスタンスでアクセスされる場合、そのインスタンスは適切な引数として渡され、所有者はそのインスタンスのクラスです。

一方、クラスを介してアクセスする場合、インスタンスは None であり、所有者のみが渡されます。プロパティ オブジェクトはこれを認識し、self を返します。

したがって、 のコードclasspropertyは実際には の一般化であり、その部分propertyが欠けています。if instance is None

于 2013-07-31T02:12:27.153 に答える
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)

このアプローチの 1 つの制限は、クラス自体ではなく、クラスのインスタンスに対してのみ機能することです。

>>> Planet.EARTH.G
6.673e-11
>>> Planet.G
<property object at 0x7f665921ce58>
于 2013-07-28T18:05:21.987 に答える