🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

3回答

1723閲覧

[Python3] 入力(文字列)に応じて呼び出すメソッドを変える処理を、もっとわかりやすく

siruku6

総合スコア1382

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2020/12/27 09:18

編集2021/01/02 06:59

やりたいこと

if分を使わずに入力(文字列)に応じて呼び出すメソッドを変える処理を書いたのですが、他の書き方に書き変えたいです。

できたこと

以下のような処理を実装することはできました。

python

1def win(): 2 return 'you win !' 3 4def lose(): 5 return 'you lose...' 6 7def draw(): 8 return 'draw game' 9 10 11# result が出るまで色々があるが、省略したものとする 12result = 'win' 13 14# if/else/elseを書きたくないので、代わりに辞書で処理させた 15method_dict = { 16 'win': win, 17 'lose': lose, 18 'draw': draw 19} 20 21method_dict.get('win')() 22=> 'you win !'

ここから先どうしたいか

method_dict.get('win')()
このメソッド呼び出し方が非常に醜く、一般的ではない書き方になっているように感じています。

もう少し初心者でもわかりやすい、もしくは一般的な書き方にできたらと思っているのですが、何か良いアイデアお持ちの方いらっしゃらないでしょうか.....?

補足

2回目ですが、if分を使わずに処理を書きたいです。
理由としては、私の書く処理が循環的複雑度(cyclomatic complexity)が高く、いつもLintツールに怒られているからです。

できるだけ循環的複雑度を抑えつつ、なるべく初心者でも理解できるような書き方にしたいです。

目指しているもの

  • if文を使わない
  • なるべく初心者でも理解しやすい
  • 循環的複雑度が低くなるようにする

lambda関数とか使ったらもう少しわかりやすくなったりしないでしょうか....。


追記 (2021/01/02)

ご指摘やいくつかの回答のおかげで、具体的なソースがないと回答のしようがないことがわかりましたので、ソースコードのサンプルを掲載します。

※実際のコードではないので、そのまま実行しても動作しないかエラーになる可能性があります。
実際のコードは、各ファイルが数百行に及び、かなり可読性も低い状態のため、大幅に省略することにしました。

ソースの概要

  • 以下にクラス図を示します

ここ数日で勉強したばかりなので、間違っていたらすみません
クラス図

  • 上記の図は以下の関係性を表しています

・CallerA has Manager
・CallerB has Manager
・Manager has ClientA and ClientB
・ManagerはFacadeのつもりです
・今回の質問とは関係ないですが、Managerは、ClientA, ClientBの処理結果を統合してCallerに返却する他のメソッドも多数持っています

上図のようなクラス設計にした理由(追記2回目)

クラス設計の理由を書いていなかったので解説します。
当初はCallerA, B...という複数のクラスが、各Clientを五月雨式に呼び出していました。

しかし、各Caller間で重複する処理が出てきたことや、可読性が大幅に低下したことがきっかけで、CallerとClientの間に中間クラス(Manager)を設けることを考えつきました。

※この中間クラスをFacadeなどと呼ぶこともあると知ったのはここ数日の話なので、もしかしたらこの呼称は間違っているかもしれません。

各クラスのソース

python

1# CallerA 2from manager import Manager 3 4 5class CallerA(): 6 def __init__(self): 7 self.manager = Manager() 8 9 def some_method(self): 10 self.manager.call_client('aaa', variable_a='a', variable_b='b') 11 return result 12# 以下略

python

1# CallerB 2from manager import Manager 3 4 5class CallerB(): 6 def __init__(self): 7 self.manager = Manager() 8 9 def some_method_a(self): 10 self.manager.call_client('aaa', variable_a='aa', variable_b='bb') 11 return result 12 13 def some_method_b(self): 14 self.manager.call_client('bbb') 15 return result 16 17 def some_method_c(self): 18 self.manager.call_client('ccc', variable_c='c') 19 return result 20# 以下略

python

1# Manager 2from clients.client_a import ClientA 3from clients.client_b import ClientB 4 5 6class Manager(): 7 def __init__(self): 8 self.client_a = ClientA() 9 self.client_b = ClientB() 10 11 def call_client(self, method, **kwargs): 12 method_dict = { 13 'aaa': self.client_a.return_a, 14 'bbb': self.client_b.return_b, 15 'ccc': self.return_c, 16 } 17 return method_dict.get(method)(**kwargs) 18 19 def return_c(self, variable_c): 20 # variable_c を使用する処理 21 c = .... 22 return c 23# 以下略

python

1# ClientA 2class ClientA(): 3 def __init__(self): 4 pass 5 6 def return_a(self, variable_a, variable_b): 7 # variable_a, variable_b を使用する処理 8 a = .... 9 return a 10# 以下略

python

1# ClientB 2class ClientB(): 3 def __init__(self): 4 pass 5 6 def return_b(self): 7 # 何らかの処理 8 b = .... 9 return b 10# 以下略

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hentaiman

2020/12/27 11:37

この質問内容に対する二名の回答に対する明確な不満点が分かりません。 既に回答中でも指摘されてますが、実際のコードを載せる事を勧めます。 それが不可能なら要点要件漏れの無いサンプルコードを質問に書く必要があります。
siruku6

2020/12/27 11:47 編集

>実際のコードを載せる かなり複雑でどこを切り出したらいいかわからないので省略していましたが、いったん丸々掲載することも検討します。 申し訳ないですが、要件漏れのないサンプルコードはおそらく私には作成できそうにないです。 >二名の回答に対する明確な不満点 qnoirさんの分に関しては、`getattr(sys.modules[__name__], command)`がpythonの内部まで踏み込みすぎていると感じたためです。 このような書き方の処理をあちこちに乱発しては困るので、なるべく使いたくないです。 これに関しては要件ではなくて、あくまでも私個人の偏見と独断と感覚にすぎません。 otnさんの方に関しては、確かに情報不足でこのままではどうしようもないように感じましたので、何かしらうまく問題の箇所を掲載できるよう試みます。 ※大変申し訳ないですが、早くても来週の土日以降となりそうです。
hentaiman

2020/12/27 12:09

> これに関しては要件ではなくて、あくまでも私個人の偏見と独断と感覚にすぎません。 この基準が明らかにされないと回答付かなさそうだけど、例えば呼び出す側はどういう書き方で呼び出せるのが理想なんですか?
TakaiY

2020/12/28 09:59

まだ明確な判断基準が出てくるの待ち状態のようですが。 応答で帰ってくる文字列などによって処理を分岐したくて、if文を使いたくないのであれば、辞書にしてgetなり[]なりで取り出す方法をとることは「非常に醜く、一般的ではない書き方」ということはなく、定番で普通でわかりやすいと思います。
siruku6

2021/01/02 03:44 編集

>hentaimanさん:「この基準」 rubyのeval()やsend()などはセキュリティ上のリスクがあるので使用しないことが好ましいと考えておりました。 それと同様に、pythonにおいても、文字列をそのままメソッド名として実行するのはセキュリティ上のリスクがあるのではないか、と考えて、それに類似する`getattr(sys.modules[__name__], command)`などは使用したくないと考えています。 これが偏見と独断と感覚の内容になります。 >TakaiYさん:「if文を使いたくないのであれば、辞書にしてgetなり[]なりで取り出す方法をとることは「非常に醜く、一般的ではない書き方」ということはなく、定番で普通でわかりやすいと思います。」 pythonにおける一般的な書き方がわからなかったため、こういった指摘もありがたいです。 他にどんな書き方があるのかわからなかったため、いろいろな書き方を知りたいと思い、今回の質問をさせていただいておりました。 >実際のコードを載せる これは準備中です。→なるべく余計なものを削除した状態でソースコードのサンプルも掲載しました。
TakaiY

2021/01/02 07:17

ざっと見てみました。 局所的な話としてであれば、想定している初心者のレベルがわかりませんが、if分を使うべきですし、関数が第一級オブジェクトであるpythonであれば、mapによるディスパッチはそれほど難しいとは思いませんので、現状のままで充分だと思います。 ただ、構成全体をみると、CallerとClientの関連が強すぎるのが気になります。相対的にManagerの 役割が薄いのでこのようなディスパッチになってしまうのではないでしょうか。とはいえ、どのようにすればいいかについてアドバイスできるほどの力も無いのでここでコメントとさせていただきます。
siruku6

2021/01/02 09:42 編集

TakaiYさんありがとうございます。 >想定している初心者のレベルがわかりませんが、if分を使うべき 確かに...本当に初心者向けにするのであればそれが一番わかりやすいですね。 >mapによるディスパッチはそれほど難しいとは 辞書型の各値にメソッドを代入しておいた今回の操作のことを指していると認識しました。ありがとうございます。 >構成全体をみると、CallerとClientの関連が強すぎる ありがとうございます。これはもしかしたらそうかもしれないと思っていましたが、明確にコメントいただけたのは非常にありがたいです。 managerは、実際は複数のClientの出力結果をmergeしたりフィルターにかける処理も別メソッドで担っているのですが、むしろそれが逆に設計としてまずいのかどうか、なども気にしていました。 この問題は今回の質問とは関係ないためあまりここでやり取りするのは望ましくなさそうですが、そちらも問題ととらえて取組んでまいります。
siruku6

2021/01/02 09:42

>みなさま たくさんの回答やコメントを下さりありがとうございました。 ベストアンサー以外にも、知らなかった書き方、考え方を学ぶことができ大変うれしく思っております。 今回の対策として直接採用しないものであったとしても、今後いつどれが役に立つかわからないので、ここで教えていただいたことを引き出しにしまって先に進んで参ります。
hentaiman

2021/01/03 01:16

> Managerは、ClientA, ClientBの処理結果を統合してCallerに返却する他のメソッドも多数持っています 主にこれのせいで訳わからん作りが必要になっているのでは?意見のひとつとして設計ミスを挙げておきます。 そういう設計をせざるを得ない場合もあるのかもしれないですが、修正後の質問内容からも汲み取れないので、飽くまで一意見です。 個人的には後戻り可能(設計見直し可能)ならやり直してみてはいかがでしょうか。 ※もちろん今の設計がもっとも適切な可能性もあります
siruku6

2021/01/03 03:09

>後戻り可能(設計見直し可能)ならやり直してみてはいかがでしょうか。 ありがとうございます。 私も今のままの設計がいいのか悪いのかよくわからない(しかも直感的には悪いと感じている)ので、何とかしたいと思っていました。 そこで、数日前からデザインパターンを勉強し始めたところなのですが、まだ2つのパターンを理解しただけで、使えるレベルにもなっていなければ、他にも多数のパターンがあるかと思うので、少しずつ学んで設計も改善していきたいと思います。
guest

回答3

0

ベストアンサー

あえて eval を使ってみました。

p.py

python3

1 2def win(): 3 return 'you win !' 4 5def lose(): 6 return 'you lose...' 7 8def draw(): 9 return 'draw game' 10 11def print_method_ret(name): 12 if not name in VALID_NAME: 13 raise NameError(name) 14 15 print(eval(f"{name}()")) 16 17for name in ['win', 'lose', 'draw', 'bad']: 18 print_method_ret(name)

実行例
イメージ説明

win, lose draw 以外の文字がわたされたらエラーになるようにはしています。

追記

参考情報

  • Pythonで関数名を動的に作成し、実行する

https://blog.ikappio.com/dynamically-create-and-execute-function-name-in-python/

  • Pythonのeval()とast.literal_eval()を使用していますか?

https://qastack.jp/programming/15197673/using-pythons-eval-vs-ast-literal-eval

投稿2020/12/28 10:59

編集2021/01/03 00:48
katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

siruku6

2021/01/02 04:01

回答ありがとうございます。 pythonにもevalメソッドがあるんですね。 rubyでは、evalにはセキュリティ上のリスクがあるので使用しない方が良いものとされていたので、あまり使用したくはないとも思いますが、pythonでの扱いがどうなっているのかまだ把握していないので、調べてみたいと思います。 (「あえて eval を」と書かれているところを見ると、おそらく何らかの理由で推奨されていないのだろうと推測しています...)
siruku6

2021/01/02 07:11

回答の追記ありがとうございます。 可読性だけを考慮すればevalはわかりやすい書き方ですし初心者にも容易に理解できるものだと感じています。 質問文の要件には記載していなかったですが、セキュリティの観点からevalの使用を控えたいとなると、今回記載していただいたliteral_evalが候補として有力になってくるかと感じました。
siruku6

2021/01/02 09:37

今回質問の趣旨に一番近い回答をしていただいたのでkatoyさんにベストアンサーをつけさせていただきました。
otn

2021/01/02 10:17

> pythonでの扱いがどうなっているのかまだ把握していないので、 rubyでもpythonでも他の言語でも、evalのリスクは同じです。 他の方法が不可能と言うとき以外は使わないです。
siruku6

2021/01/02 10:28

>otnさん >他の方法が不可能と言うとき以外は使わないです。 ありがとうございます。 evalはやはり危険なものには変わりないんですね...。 ------------------- 実際使うかどうかは別として、literal_evalを調べてみたところ、どうやら変数を展開することができないようでした。 また、これもevalでは可能なのですが、literal_evalに渡す文字列内に関数を記載しても実行できませんでした。 literal_evalの方がその分安全といえるとは思いますが、利用できる場面はかなり限られそうです。
guest

0

Python

1result = 'win' 2(中略) 3method_dict[result]()

とかするんじゃ無くて、

Python

1result = win 2(中略) 3result()

でいいのでは?

投稿2020/12/27 10:05

otn

総合スコア85890

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

siruku6

2020/12/27 11:26 編集

ありがとうございます。 すみません、条件には記載していないのですが、resultは別ファイル(A)から渡されるoutputなため、resultにファイルBのメソッドを直接代入することができません。 また、winなどのメソッドはかなり複雑なものなので、各ファイルで別々に宣言するのはちょっと不適切です。 ちょっと省略しすぎました.... ただ、確かにresultは文字列ではある必要はないです。 あまりやりたくはないですが数値でも問題ないです。 rubyならシンボルにしたいところでした。
otn

2020/12/27 12:13

result は別プログラムから受け取るので、文字列になるということですね。 では、 method_dict[result]() かな。
guest

0

import sys def do(command): try: method = getattr(sys.modules[__name__], command) return method() except Exception: return

つかいかた

def win(): return 'you win !' def lose(): return 'you lose...' def draw(): return 'draw game' # result が出るまで色々があるが、省略したものとする result = 'win' print(do(result))

追記

元のmethod_dict.get('win')()より100倍直観的だと思ったんですが、わかりにくいという指摘があったのであえて根本的な問題提起を追記します。

そもそもこの質問は質問として実用的な問題ではないと思います。
「勝ち・負け・引き分け」を表す文字列結果データに応じて、「勝ちです・負けです・引き分けです」という、結果に対応するメッセージを、勝ち・負け・引き分けという名前の付いた各関数で返す、という操作をif文を使わずに書け、という問題です。

上記のシンプルな問題をそのままで解決するなら結果に対するメッセージを辞書で対応させればいいだけの話と思います。

result_messages = { 'win':'you win !', 'lose': 'you lose...', 'draw': 'draw game' } # なんやかんや計算して結果は勝ちと出たようです。 result = 'win' mes = result_messages.get(result) assert mes is not None # 勝負結果のメッセージを表示します。 print(mes)

私個人は超能力者ではないので、質問に書いてあるシンプルな問題を敷衍して、一般的実用的な問題に拡張し、質問者様がご満足されるような一般的な答えを出すことはできません。

複雑度が大きすぎてLintに怒られるから、というのも状況も理解に苦しむのですが、本質的に解決したいならば、その「困っている」現状のソースをそのまま掲載した方が早いのではないでしょうか。

投稿2020/12/27 09:34

編集2020/12/27 10:16
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

siruku6

2020/12/27 09:37

アイデアありがとうございます! ちょっとした裏側に踏み込んだ処理になっていますね。 あまり詳しくはないですが、メタプログラミングに近い書き方と言えるのでしょうか。 ただ残念ながら、どちらかというとさらに理解しにくくなっているように感じましたので、他のアイデアを待ちたいと思います。
siruku6

2021/01/02 06:51 編集

>元のmethod_dict.get('win')()より100倍直観的 私の言い方が良くなかったのかと想像しています。批判しているつもりはないのですが、おそらくそのように受け取られたのではないかと思います。 もしそうでしたら申し訳ないです。 理想としては、pythonの初心者向けの参考書や教科書に載っている程度の文法で可能な限り実装することを目指しており、その意図で「なるべく初心者でも理解しやすい」という希望を質問内に記載しておりました。 `getattr(sys.modules[__name__], command)`は、この意図からは若干外れてしまうところがあると感じたため、他のアイデアを待つこととしました。 >そもそもこの質問は質問として実用的な問題ではない 可読性をいかに上げるか、で悩んだ結果の質問でしたが、pythonの初心者でもわかりそうな文法のみに制限しようとするのは実用的ではない、と言われれば、確かにそうかもしれないです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問