|
1: 2019-05-26 (日) 19:35:44 njf |
| + | *Flag [#h3172853] |
| | | |
| + | 注意:FlagやIntFlagは列挙型Enumの一種です。Enumを知らない場合は[[Python/列挙型]]を先に読むのを推奨します。 |
| + | |
| + | 昔からあるプログラムの書き方で、ビット毎にフラグを設定して一つの変数に複数のフラグを保存する方法があります。 |
| + | |
| + | ビット単位で管理するのでメモリが節約できます。また一般にビット演算は高速なのでプログラムの高速化も狙えます。 |
| + | |
| + | これと同じようなことを実現するのPythonのクラスがFlagです。 |
| + | |
| + | 例えば、ゲームで状態異常を定義したいとして、次のようなクラスを定義します。 |
| + | |
| + | |
| + | from enum import Flag |
| + | |
| + | class PlayerStatus(Flag): |
| + | SLEEP = 1 #睡眠 |
| + | POISON = 2 #毒 |
| + | PARALYSIS = 4 #麻痺 |
| + | |
| + | ここで1,2,4は二進数で書けば1,10,100の事で、それぞれ1ビット目、2ビット目,3ビット目をフラグとして使っています。 |
| + | |
| + | このように手で書くこともできますが、Python3.6で追加されたautoという関数を使うと自動で二のべき乗を割り振ってくれます。 |
| + | |
| + | from enum import Flag ,auto |
| + | |
| + | class PlayerStatus(Flag): |
| + | SLEEP = auto() |
| + | POISON = auto() |
| + | PARALYSIS = auto() |
| + | |
| + | for s in PlayerStatus: |
| + | print(s.name,s.value) |
| + | |
| + | 結果: |
| + | |
| + | SLEEP 1 |
| + | POISON 2 |
| + | PARALYSIS 4 |
| + | |
| + | |
| + | これの良いところは一つの変数に複数のフラグを設定できることです。 |
| + | 例えば、睡眠かつ毒状態のステータスは |
| + | |
| + | status = PlayerStatus.SLEEP | PlayerStatus.POISON |
| + | |
| + | と書きます。これを表示させると、 |
| + | |
| + | |
| + | print(status) |
| + | |
| + | 結果: |
| + | |
| + | PlayerStatus.POISON|SLEEP |
| + | |
| + | というふうに、フラグの状態をわかりやすく出してくれるのでデバッグの時便利です。 |
| + | |
| + | フラグが立っているかどうかは「&」でチェックします。 |
| + | |
| + | status = PlayerStatus.SLEEP | PlayerStatus.POISON |
| + | |
| + | if status & PlayerStatus.SLEEP: |
| + | print("SLEEP!") |
| + | |
| + | if status & PlayerStatus.POISON: |
| + | print("POISON!") |
| + | |
| + | if status & PlayerStatus.PARALYSIS: |
| + | print("PARALYSIS!") |
| + | |
| + | 結果: |
| + | SLEEP! |
| + | POISON! |
| + | |
| + | フラグをオフにするには排他的論理和「^」を使います。 |
| + | |
| + | status = PlayerStatus.SLEEP | PlayerStatus.POISON |
| + | |
| + | status = status ^ PlayerStatus.SLEEP |
| + | |
| + | if status & PlayerStatus.SLEEP: |
| + | print("SLEEP!") |
| + | |
| + | if status & PlayerStatus.POISON: |
| + | print("POISON!") |
| + | |
| + | if status & PlayerStatus.PARALYSIS: |
| + | print("PARALYSIS!") |
| + | |
| + | |
| + | 結果: |
| + | POISON! |
| + | |
| + | フラグをオフにするには以下のように「&」と「~」(否定)を組み合わせる方法も良く行われます。 |
| + | |
| + | status = PlayerStatus.SLEEP & (~PlayerStatus.POISON) |
| + | |
| + | また、Flag型はFlag型同士でなければ演算できないことに注意してください。 |
| + | Intと演算したいときには次のIntFlag型を使います。 |
| + | |
| + | *IntFlag [#d713d3b4] |
| + | |
| + | IntFlag型はFlag型とほぼ同じ機能がありますが、Flag型とは異なり整数としても扱えます。 |
| + | |
| + | つまり、Flag型では次の処理はエラーとなります。 |
| + | |
| + | status = PlayerStatus.SLEEP | 2 |
| + | |
| + | print(status) |
| + | |
| + | Flag型での結果: |
| + | Traceback (most recent call last): |
| + | File "enumflgs.py", line 50, in <module> |
| + | status = PlayerStatus.SLEEP | 2 |
| + | TypeError: unsupported operand type(s) for |: 'PlayerStatus' and 'int' |
| + | |
| + | しかし、IntFlag型なら問題なく実行されます。 |
| + | |
| + | IntFlag型での結果: |
| + | |
| + | PlayerStatus.POISON|SLEEP |
| + | |
| + | 直接数値が使えないのでFlag型の方がマジックナンバーが発生しにくいという利点があります。 |
| + | |
| + | 一方でIntFlag型は計算結果や外部入力の数値をそのままフラグとして演算できるという利点があります。 |
| + | |
| + | 基本的にFlag型を使い、フラグを整数と演算する必要がある場合はIntFlag型を使うというのが良いかもしれません。 |
| + | |
| + | *まとめ [#peb2d989] |
| + | |
| + | Flagはビット演算でのフラグの管理を使いやすくするクラスです。 |
| + | IntFlagはそのFlagを整数としても扱えるようにした物です。 |
| + | |
| + | フラグがたくさんあって整理が難しい場合や、少しでも処理を高速化したい場合には便利なクラスです。 |
| + | |
| + | ただし、現在のようにメモリも大容量でCPUも速くなった時代にフラグ処理が少し軽くなる程度の工夫が必要かどうかはちょっと微妙な気もします。 |
| + | |
| + | フラグが少ない場合は真偽型boolを使う方が楽です。 |