Pythonで列挙を行うための認識された方法は何ですか?
たとえば、ゲームを書いているときに、「上」、「下」、「左」、「右」に移動できるようにしたいと考えています。文字列を使用しているのは、Pythonで列挙型がどのように機能するのかまだわかっていないためです。そのため、私のロジックには次のようなものが散らかっています。
def move(self, direction):
if direction == "up":
# Do something
置き換えたい"up"
のようなものDirections.up
class Directions:
up = 0
down = 1
left = 2
right =3
UPDATE 1:Python 3.4には、よく設計された組み込みの 列挙型ライブラリ が含まれます。値は常にその名前とタイプを知っています。整数互換モードがありますが、新しい使用法として推奨されるデフォルトはシングルトンであり、他のオブジェクトとは異なります。
UPDATE 2:これを書いてから、列挙型の重要なテストはserializationであることがわかりました。他の側面は後でリファクタリングできますが、列挙型がファイル内/ネットワーク上にある場合は、それが古い/新しいバージョン(異なる値のセットをサポートしている可能性がある)によって逆シリアル化された場合に何が起こるかを前もって考えてください...
Enumが必要だと確信している場合は、他の人が方法で答えています。しかし、なぜそれらが欲しいのか見てみましょうか?動機を理解すると、ソリューションの選択に役立ちます。
アトミック値-Cでは、小さい数は簡単に渡すことができますが、文字列はそうではありません。 Pythonでは、「up」のような文字列は多くの用途に完全に適しています。さらに、数字だけで終わるソリューションはデバッグにとってはより悪いです!
意味のある値-Cでは、頻繁に既存のマジックナンバーを処理する必要があり、いくつかのそのための構文砂糖。ここではそうではありません。ただし、には、ルートに関連付ける他の意味のある情報があります。 (dx、dy)ベクトル-詳細は以下をご覧ください。
タイプチェック-Cでは、列挙型はコンパイル時に無効な値をキャッチするのに役立ちます。しかし、Pythonは、一般的に、タイピングを減らすためにコンパイラチェックを犠牲にすることを好みます。
イントロスペクション(C列挙には存在しない)-すべての有効な値を知りたい。
したがって、Pythonicソリューションの軽い面では、文字列を使用し、おそらくすべての有効な値のリスト/セットを持っています。
DIRECTIONS = set(['up', 'down', 'left', 'right'])
def move(self, direction):
# only if you feel like checking
assert direction in DIRECTIONS
# you can still just use the strings!
if direction == 'up':
# Do something
デバッガーは、関数が 'up'を引数として呼び出されたことを通知することに注意してください。 direction
が実際に0
である解決策は、これよりも悪いです。
LISPファミリーの言語では、この使用法はsymbolsと呼ばれます-アトミックオブジェクトは数値と同じくらい簡単に使用できますテキスト値。 (正確には、シンボルは文字列に似ていますが別のタイプです。ただし、PythonはLISPがシンボルを使用する場所で通常の文字列を日常的に使用します。)
'up'
が0
より優れているという考えを他のソリューションと組み合わせることができます。
スペルミスを検出したい場合(実行時):
UP = 'up'
...
RIGHT = 'right'
そして、プリフィックスを入力して補完を得たい場合は、上記をクラスに入れます:
class Directions:
UP = "up"
...
RIGHT = "right"
または別のファイルに入れて、モジュールにします。
モジュールを使用すると、怠惰なユーザーがfrom directions import *
を実行してプレフィックスをスキップできます。これをプラスとマイナスのどちらにするかはあなた次第です(個人的には、頻繁に使用している場合はDirections.UP
を入力するのが嫌いです)。
各値に関連する有用な情報/機能がある場合はどうなりますか? 「右」は4つの任意の値の1つだけではなく、X軸の正の方向です。
その中でif
が次のようなものである場合:
def move(self, direction):
if direction == 'up':
self.y += STEP
Elif direction == 'down':
self.y -= STEP
Elif direction == 'left':
self.x -= STEP
Elif direction == 'right':
self.x += STEP
あなたが本当に書きたいものよりも:
def move(self, direction):
self.x += direction.dx * STEP
self.y += direction.dy * STEP
以上です!
したがって、これをinstancesに詰め込みます:
# Written in full to give the idea.
# Consider using collections.namedtuple
class Direction(object):
def __init__(self, dx, dy, name):
self.dx = dx
self.dy = dy
self.name = name
def __str__(self):
return self.name
UP = Direction(0, 1, "up")
DOWN = Direction(0, -1, "down")
LEFT = Direction(-1, 0, "left")
RIGHT = Direction(1, 0, "right")
または単にclasses:
class Direction(object):
pass
class Up(Direction):
dx = 0
dy = 1
...
class Right(Direction):
dx = 1
dy = 0
Pythonでは、クラスはオブジェクト(他のオブジェクトとは異なる)でもあり、direction == Up
などで比較できます。
一般に、インスタンスはおそらくよりクリーンですが、列挙された概念に階層関係がある場合、クラスを使用してそれらを直接モデル化することは非常に便利です。
私はクーゲルに+1を与えましたが、別の無駄のないオプションは
_dirUp, dirDown, dirLeft, dirRight = range(4)
_
だから私は考えていました...明らかにLHSで4つの項目を指定し、次に再びRHSで4つを指定するという点で、DRY違反があります。項目を追加するとどうなるか将来?他の誰かがそれらを追加するとどうなりますか、そしておそらく彼らは私たちよりずさんなものですか?DRY違反を削除する明らかな方法の1つは、列挙自体のリストを使用して値を割り当てることです:
_>>> enums = ['dirUp', 'dirDown']
>>> for v, k in enumerate(enums):
... exec(k + '=' + str(v))
...
>>> print dirDown
1
>>> print dirUp
0
_
これにexec()
を使用してお腹が空くなら、問題ありません。そうでない場合は、他のアプローチを使用します。この現在の議論はとにかくすべて学術的です。ただし、まだ問題があります。列挙型が多数のソースコード全体で使用されており、他のプログラマがやって来て、dirUp
とdirDown
の間に新しい値を挿入するとどうなりますか?列挙型の名前と列挙型自体の間のマッピングが間違っているため、これは悲惨を引き起こします。元の単純なソリューションでも問題が残ることに注意してください。
ここでは、組み込みのhash()
関数を使用してenum値をintとして決定するという新しいアイデアがあり、enum自体のテキスト名を使用してハッシュを決定しています。
_>>> for k in enums:
... exec(k + '=' + str(hash(k)))
...
>>> dirUp
-1147857581
>>> dirDown
453592598
>>> enums = ['dirUp', 'dirLeft', 'dirDown']
>>> for k in enums:
... exec(k + '=' + str(hash(k)))
...
>>> dirUp
-1147857581
>>> dirDown
453592598
>>> dirLeft
-300839747
>>>
_
dirUp
とdirDown
の間に新しい値、つまりdirLeft
と最初の2つの元のマッピング値変更なしを挿入したことに注意してください。
これを自分のコードで実際に使用する場合があります。質問を投稿してくれたOPに感謝します。
Beni Cherniavsky-Paskinはいくつかの非常に良いコメントをしました:
hash()
は、プラットフォーム間で安定していません(持続性アプリケーションには危険です)私は両方の意見に同意する傾向があります。彼の提案は、ハッシュとして文字列自体を使用することです(私は値を使用する自己文書化動作が本当に好きです)。したがって、コードは次のようになります(一意性を強制するために、リストではなくセットを使用することに注意してください)。
_>>> items=('dirUp','dirDown','dirLeft','dirRight')
>>> for i in items:
exec('{}="{}"'.format(i,i))
>>> dirDown
'dirDown'
_
他のコードとの衝突を避けるために、これらを名前空間に置くことも簡単です:
_>>> class Direction():
for i in ('dirUp','dirDown','dirLeft','dirRight'):
exec('{}="{}"'.format(i,i))
>>> Direction.dirUp
'dirUp'
_
彼が言及している暗号化ハッシュの長さはここにあります:
_>>> from hashlib import md5
>>> crypthash = md5('dirDown'.encode('utf8'))
>>> crypthash.hexdigest()
'6a65fd3cd318166a1cc30b3e5e666d8f'
_
collections.namedtuple
オブジェクトはそのような名前空間を提供できます:
>>> import collections
>>> dircoll=collections.namedtuple('directions', ('UP', 'DOWN', 'LEFT', 'RIGHT'))
>>> directions=dircoll(0,1,2,3)
>>> directions
directions(UP=0, DOWN=1, LEFT=2, RIGHT=3)
>>> directions.DOWN
1
>>>
これはシンプルで効果的です。
class Enum(object):
def __init__(self, *keys):
self.__dict__.update(Zip(keys, range(len(keys))))
使用法:
>>> x = Enum('foo', 'bar', 'baz', 'bat')
>>> x.baz
2
>>> x.bat
3
Python 2.6+を使用している場合は、 namedtuple を使用できます。これらには、プロパティの数が固定されているという利点があります。すべての列挙値が必要な場合は、タプルのようです。
列挙値をより詳細に制御するために、独自の列挙クラスを作成できます。
def enum(args, start=0):
class Enum(object):
__slots__ = args.split()
def __init__(self):
for i, key in enumerate(Enum.__slots__, start):
setattr(self, key, i)
return Enum()
>>> e_dir = enum('up down left right')
>>> e_dir.up
0
>>> e_dir = enum('up down left right', start=1)
>>> e_dir.up
1
__slots__
を宣言するとEnumクラスがシールされ、__slots__
プロパティを持つクラスから作成されたオブジェクトにこれ以上属性を設定できなくなります。
Enum
クラスはnamedtuple
ベースにすることもできます。その場合、タプルの機能も取得できます。サブクラス化については namedtuple docs
を参照してくださいnamedtuple