web-dev-qa-db-ja.com

pythonの列挙

重複:
Pythonで「列挙型」を実装する最良の方法は何ですか?

Pythonで列挙を行うための認識された方法は何ですか?

たとえば、ゲームを書いているときに、「上」、「下」、「左」、「右」に移動できるようにしたいと考えています。文字列を使用しているのは、Pythonで列挙型がどのように機能するのかまだわかっていないためです。そのため、私のロジックには次のようなものが散らかっています。

def move(self, direction):
    if direction == "up":
        # Do something

置き換えたい"up"のようなものDirections.up

29
Justin
class Directions:
    up = 0
    down = 1
    left = 2
    right =3
45
Kugel

UPDATE 1:Python 3.4には、よく設計された組み込みの 列挙型ライブラリ が含まれます。値は常にその名前とタイプを知っています。整数互換モードがありますが、新しい使用法として推奨されるデフォルトはシングルトンであり、他のオブジェクトとは異なります。

UPDATE 2:これを書いてから、列挙型の重要なテストはserializationであることがわかりました。他の側面は後でリファクタリングできますが、列挙型がファイル内/ネットワーク上にある場合は、それが古い/新しいバージョン(異なる値のセットをサポートしている可能性がある)によって逆シリアル化された場合に何が起こるかを前もって考えてください...


Enumが必要だと確信している場合は、他の人が方法で答えています。しかし、なぜそれらが欲しいのか見てみましょうか?動機を理解すると、ソリューションの選択に役立ちます。

  • アトミック値-Cでは、小さい数は簡単に渡すことができますが、文字列はそうではありません。 Pythonでは、「up」のような文字列は多くの用途に完全に適しています。さらに、数字だけで終わるソリューションはデバッグにとってはより悪いです!

  • 意味のある値-Cでは、頻繁に既存のマジックナンバーを処理する必要があり、いくつかのそのための構文砂糖。ここではそうではありません。ただし、には、ルートに関連付ける他の意味のある情報があります。 (dx、dy)ベクトル-詳細は以下をご覧ください。

  • タイプチェック-Cでは、列挙型はコンパイル時に無効な値をキャッチするのに役立ちます。しかし、Pythonは、一般的に、タイピングを減らすためにコンパイラチェックを犠牲にすることを好みます。

  • イントロスペクション(C列挙には存在しない)-すべての有効な値を知りたい。

    • Completion-エディターは可能な値を表示し、入力を支援します。

引き換えられた文字列(別名シンボル)

したがって、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()を使用してお腹が空くなら、問題ありません。そうでない場合は、他のアプローチを使用します。この現在の議論はとにかくすべて学術的です。ただし、まだ問題があります。列挙型が多数のソースコード全体で使用されており、他のプログラマがやって来て、dirUpdirDownの間に新しい値を挿入するとどうなりますか?列挙型の名前と列挙型自体の間のマッピングが間違っているため、これは悲惨を引き起こします。元の単純なソリューションでも問題が残ることに注意してください。

ここでは、組み込みの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
>>> 
_

dirUpdirDownの間に新しい値、つまりdirLeftと最初の2つの元のマッピング値変更なしを挿入したことに注意してください。

これを自分のコードで実際に使用する場合があります。質問を投稿してくれたOPに感謝します。

      • (少し時間が経過します)

Beni Cherniavsky-Paskinはいくつかの非常に良いコメントをしました:

  • Pythonのデフォルトの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'
_
17
Caleb Hattingh

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
>>> 
14
gimel

これはシンプルで効果的です。

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
13
Robert Rossney

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

7
Imran