なぜPython辞書内に関数を保存するのですか?


68

私はpython初心者であり、辞書と関数を含むテクニックを学びました。構文は簡単で、些細なことのように思えますが、私のPythonの感覚はチクチクしています。これは深くて非常にパイソン的な概念であり、その重要性を十分に把握していないということです。誰かがこのテクニックに名前を付けて、どのように/なぜそれが役立つかを説明できますか?


テクニックは、Python辞書とそれに使用する予定の関数がある場合です。値が関数の名前である追加の要素を辞書に挿入します。関数を呼び出す準備ができたら、名前ではなく、dict要素を参照して間接的に呼び出しを発行します。

私が取り組んでいる例は、Learn Python the Hard Way、2nd Edのものです。(これは、Udemy.comからサインアップしたときに利用可能なバージョンです。残念ながら、ライブの無料HTMLバージョンは現在Ed 3であり、この例は含まれていません)。

言い換えると:

# make a dictionary of US states and major cities
cities = {'San Diego':'CA', 'New York':'NY', 'Detroit':'MI'}

# define a function to use on such a dictionary
def find_city (map, city):
    # does something, returns some value
    if city in map:
        return map[city]
    else:
        return "Not found"

# then add a final dict element that refers to the function
cities['_found'] = find_city

その場合、次の式は同等です。関数を直接呼び出すことも、値が関数であるdict要素を参照することもできます。

>>> find_city (cities, 'New York')
NY

>>> cities['_found'](cities, 'New York')
NY

誰かがこれがどの言語機能であるか、そしておそらくそれが「実際の」プログラミングのどこでプレイするようになるのかを説明できますか?このおもちゃの練習は構文を教えるのに十分でしたが、そこまで私を連れて行きませんでした。


13
なぜこの投稿はトピックから外れているのでしょうか?これは素晴らしいアルゴリズムとデータ構造の概念の質問です!
マーティンピーターズ

私は他の言語でこのようなものを見てきました(そしてしました)。それをswitchステートメントと見なすこともできますが、O(1)ルックアップ時間のある適切なオブジェクトにうまくラップされています。
KChaloux

1
独自の辞書内に関数を含めることについて重要で自己参照的なものがありました... @dietbuddhaの答えをご覧ください...
mdeutschmtl

回答:


83

dictを使用すると、キーを呼び出し可能オブジェクトに変換できます。ただし、例のように、キーをハードコードする必要はありません。

通常、これは呼び出し側ディスパッチの形式であり、変数の値を使用して関数に接続します。ネットワークプロセスがコマンドコードを送信するとします。ディスパッチマッピングを使用すると、コマンドコードを実行可能コードに簡単に変換できます。

def do_ping(self, arg):
    return 'Pong, {0}!'.format(arg)

def do_ls(self, arg):
    return '\n'.join(os.listdir(arg))

dispatch = {
    'ping': do_ping,
    'ls': do_ls,
}

def process_network_command(command, arg):
    send(dispatch[command](arg))

ここで呼び出す関数は、値が何であるかに完全に依存することに注意してくださいcommand。キーも一致する必要はありません。文字列である必要はありません。キーとして使用できるものなら何でも使用でき、特定のアプリケーションに適合します。

ディスパッチメソッドの使用は、などのその他の手法よりも安全です。eval()これは、コマンドを事前に定義したものに制限できるためです。ls)"; DROP TABLE Students; --たとえば、攻撃者はディスパッチテーブルを通過してインジェクションをこっそりとするつもりはありません。


5
@Martjin-その場合、これを「コマンドパターン」の実装と呼ぶことはできませんか?それがOPが把握しようとしている概念だと思われますか?
PhD

3
@PhD:ええ、私が作成した例はコマンドパターンの実装です。dictディスパッチャとして動作(コマンド管理、呼出し、等)。
マーティンピーターズ

すばらしいトップレベルの説明、@ Martijn、ありがとう。私は「発送」のアイデアを得たと思う。
mdeutschmtl

28

@Martijn Pietersはテクニックを説明してくれましたが、あなたの質問から何かを明確にしたかったです。

知っておくべき重要なことは、辞書に「関数の名前」を保存していないことです。関数自体への参照を保存しています。printon関数を使用してこれを確認できます。

>>> def f():
...   print 1
... 
>>> print f
<function f at 0xb721c1b4>

f定義した関数を参照する変数です。辞書を使用すると、同じようなものをグループ化できますが、関数を別の変数に割り当てることと何も変わりません。

>>> a = f
>>> a
<function f at 0xb721c3ac>
>>> a()
1

同様に、関数を引数として渡すことができます。

>>> def c(func):
...   func()
... 
>>> c(f)
1

5
ファーストクラスの機能に言及することは間違いなく役立ちます:-)
フロリアンマーゲイン

7

Pythonクラスは実際には辞書の単なる構文シュガーであることに注意してください。行うとき:

class Foo(object):
    def find_city(self, city):
        ...

あなたが電話するとき

f = Foo()
f.find_city('bar')

本当に同じです:

getattr(f, 'find_city')('bar')

これは、名前解決後、次と同じです:

f.__class__.__dict__['find_city'](f, 'bar')

1つの便利なテクニックは、ユーザー入力をコールバックにマッピングすることです。例えば:

def cb1(...): 
    ...
funcs = {
    'cb1': cb1,
    ...
}
while True:
    input = raw_input()
    funcs[input]()

これは、代わりにクラスで書くことができます:

class Funcs(object):
    def cb1(self, a): 
        ...
funcs = Funcs()
while True:
    input = raw_input()
    getattr(funcs, input)()

どちらのコールバック構文が良いかは、特定のアプリケーションとプログラマーの好みに依存します。前者はより機能的なスタイルであり、後者はよりオブジェクト指向です。前者は、関数辞書のエントリを動的に変更する必要がある場合(おそらくユーザー入力に基づいて)より自然に感じるかもしれません。動的に選択できるさまざまなプリセットマッピングのセットがある場合、後者はより自然に感じるかもしれません。


この互換性は、私が「パイソン的」だと思うようになった理由だと思います。表面に見えるものは、より深いものを提示する従来の方法に過ぎないという事実です。たぶんそれはpythonに特有ではないかもしれませんが、pythonの演習(およびpythonプログラマー)は、この方法で言語機能について非常に多くのことを話しているようです。
mdeutschmtl

別の考えとして、Pythonに固有の何かが、互いに隣接して座っている2つの「用語」、dictリファレンス、および引数リストのように見えるものを評価する方法にありますか?他の言語で許可されていますか?これは、から5 * xへの代数の飛躍に相当するプログラミングのようなものです5x(単純な例えを許してください)。
mdeutschmtl

@mdeutschmtl:それはPythonに固有のものではありませんが、ファーストクラスの関数または関数オブジェクトを持たない言語では、辞書呼び出しに続いて関数呼び出しが可能な状況は決してありません。
ライライアン

2
@mdeutschmtl「表面に見えるものは、もっと深いものを提示する従来の方法にすぎないという事実」。-それは構文糖と呼ばれ、至る所に存在します
イズカタ

6

あなたが参照しているかもしれない私の心に飛び込む2つのテクニックがありますが、どちらも1つの言語よりも広いという点でPythonのようなものではありません。

1.情報の隠蔽/カプセル化と結合の手法(通常、それらは手をつないで行くので、それらを一緒にまとめています)。

データを持つオブジェクトがあり、データと密接に結合しているメソッド(動作)をアタッチします。関数の変更、機能の拡張、または呼び出し元が変更する必要のないその他の変更を行う必要がある場合(追加のデータを渡す必要がない場合)。

2.ディスパッチテーブル

関数を持つエントリは1つしかないため、古典的なケースではありません。ただし、ディスパッチテーブルは、キーによってさまざまな動作を整理するために使用され、動的に検索して呼び出すことができます。動的な方法で関数を参照していないので、これについてあなたが考えているかどうかはわかりませんが、それでも効果的な遅延バインディング(「間接」呼び出し)を得ることができます。

トレードオフ

注意すべきことの1つは、キーの既知の名前空間で何をすればうまく機能するかです。ただし、未知のキーの名前空間を持つデータと関数の間の衝突のリスクがあります。


dict(ionary)に格納されているデータと関数は関連しているため、カプセル化。データと機能は2つの非常に異なるdommainsからのものであるため、一見したところ、この例は大きく異なるエンティティをまとめているように見えます。
ChuckCottrill

0

私は非常に一般的であると思うこのソリューションを投稿し、特定のケースに適応するのが簡単で簡単に保たれるので便利です:

def p(what):
    print 'playing', cmpsr

def l(what):
    print 'listening', cmpsr

actions = {'Play' : p, 'Listen' : l}

act = 'Listen'
cmpsr = 'Vivaldi'

actions[act].__call__(cmpsr)

各要素が関数オブジェクトであるリストを定義して、__call__組み込みメソッドを使用することもできます。インスピレーションと協力に対するすべての功績。

「偉大な芸術家は単純化器です」、アンリ・フレデリック・アミエル

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.