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
内では、それを列挙型のメンバーの1つとして追加するだけなので、代わりにメソッド内で定義されます。
クラスがこの定数を他のメソッドで使用したい場合は、そこでも定義する必要がありますが、これは明らかに理想的ではありません。
Enum
内でクラス定数を定義する方法、または同じ効果を達成するための回避策はありますか?
これは、作成された列挙の90 +%では不要な高度な動作です。
ドキュメントによると:
許可されるルールは次のとおりです。
_sunder_
の名前(単一のアンダースコアで開始および終了)はenumによって予約されており、使用できません。列挙内で定義された他のすべての属性は、__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)
そして、使い捨ての列挙型の例:
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
そして最後に、多目的列挙型の例:
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
定数である必要がある場合(別名は再バインド不可)、 new aenum library [1]を使用して、constant
sおよびEnum
メンバーを再割り当てする試みをブロックします。
1 開示: Python stdlib Enum
、 enum34
backport 、および 高度な列挙(aenum
) ライブラリ。
最もエレガントなソリューション(IMHO)は、正しい動作を提供するためにミックスイン/基本クラスを使用することです。
Satellite
およびPlanet
。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
を作成します。
#
# 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)
_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 で説明されています
Hello.fooクラスを介してアクセスしたときに実際のプロパティオブジェクトが返される理由は、プロパティが
__get__(self, instance, owner)
特殊メソッドを実装する方法にあります。記述子がインスタンスでアクセスされる場合、そのインスタンスは適切な引数として渡され、所有者はそのインスタンスのクラスです。一方、クラスを通じてアクセスされた場合、インスタンスはNoneになり、所有者のみが渡されます。 プロパティオブジェクトはこれを認識し、自己を返します。
したがって、classproperty
のコードは実際にはproperty
を一般化したものであり、_if instance is None
_の部分がありません。
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>