質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

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

Python

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

Q&A

解決済

2回答

1597閲覧

関数をどの名前で呼び出したか知る方法

退会済みユーザー

退会済みユーザー

総合スコア0

Python 3.x

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

Python

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

3グッド

8クリップ

投稿2018/11/19 12:02

編集2018/11/19 13:45

前提・実現したいこと

pythonで関数を呼び出した時、どの名前で呼び出したか知る方法はありませんか?

たとえば関数numを定義し、zero, one, two, threeという別名を付けます。
このときに、この関数がone()で呼ばれたのかtwo()で呼ばれたのか区別する方法はないでしょうか。

該当のソースコード

python

1from inspect import stack 2 3def num(): 4 s = stack() 5 f, c = s[0].function, s[1].code_context 6 print(f, c) # num, ['print(one() + two() + three())\n'] 7 func_names = ['zero', 'one', 'two', 'three'] 8 return func_names.index(f) if f in func_names else -1 9 10zero = one = two = three = num 11print(one() + two() + three()) # 現状では-3になってしまうのを6になるようにしたい

試したこと

inspect.stackから関数呼び出し時の情報を取得できないかと考えたのですが、.functon は常に元々の関数名('num')が格納されていました。.code_contextからは呼び出し時の情報は拾ってこれるようですが、サンプルコードのように同じ行で複数回呼び出しをしている場合に区別する方法がありません。


hayataka2049さんからの確認についてですが、
今回は技術的な興味からなので、関数のみで対応する方法がないか考えています。
検索してみたところ、classを使う場合は以下のような方法を発見することができました。

pytohn

1class My_Number: 2 def __init__(self, n): 3 self.n = n # この数字で誰が呼ばれたのか区別する 4 5 def __call__(self, *args, **kwargs): 6 return self.n 7 8zero, one, two, three = [My_Number(i) for i in range(4)] 9print(one() + two() + three())
takotakot, LouiS0616, vestri👍を押しています

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

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

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

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

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

hayataka2049

2018/11/19 12:50 編集

純粋に技術的興味からの質問と理解して良いですか? 念の為に確認させてください (もし何らかの実用を意図しているなら他の方法を紹介します)
guest

回答2

0

ベストアンサー

func2 = func1

のようなコードの意味を考えてみますとグローバル環境であれローカル環境であれ

func1という識別子に結び付いた値」を取り出し、それをfunc2という名前に結び付いた「値」に設定する

という意味になります。これはどんな型の値であっても同じでありもちろん「関数」であっても(Pythonの関数は第一級関数、つまり関数自体もある種の型の値であることに注意してください)同じです。

さてzero()というのはどう評価されるかといえば

  • (1) zeroというプライマリ式の評価

結果は関数のインスタンスで、当然ながらnumに結び付いた関数インスタンスと同一になります。

  • (2) (1)で求めた関数インスタンスを呼び出す

その関数がPythonのコードで書かれたものであれば(C++で実装されたネイティブな関数でなければ)、関数インスタンスの__code__プロパティーなどからcodeオブジェクトを取り出し、それを実行スタックに記録した上でcodeオブジェクトをバイトコードインタープリタで実行する。

(1)がミソです。関数呼び出しの文法は最初のころ「関数名 ( 引数式の並び )」であると捉えるのが典型的だと思いますが、実際はそうではなく「プライマリ式 ( 引数式の並び )」です。つまり「関数名」などという言語仕様はなくて「単なるプライマリ式」を評価した結果がcallableだったのでそれを呼び出せただけです。Pythonインタープリタは「ひたすら値を計算するのが仕事」です。よってその値が何に由来したものだったかといったデバッグにしか使い道がなさそうな情報は最小限しか記録しません。おそらくPythonの設計者は「どの識別子に結び付いていた値だったか」はなくても、スタックトレース(バックトレース)で充分と考えたのだと思います。

ただし、「関数を起動した際のプログラムがどのようなものだったかを調べる」方法はなくはないです。

強引な方法

zero()のようにして起動された関数は呼び出し元のスタックフレームの中にあるcodeインスタンスのバイトコードが以下のようになっているでしょうから、

text

1 ... 2 10 LOAD_GLOBAL X (zero) 3 12 CALL_FUNCTION 0

バイトコードの中のCALL_FUNCTIONの直前の式がLOAD_GLOBAL Xであることを確認した上で識別子一覧からX番目を取り出せばその識別子がzeroであったことがわかります。かなりさぼってますがそういう実装を書いてみますと・・・

Python

1import inspect 2 3def num_test(): 4 try: 5 fr = inspect.currentframe().f_back 6 code = fr.f_code 7 bi = fr.f_lasti 8 assert code.co_code[bi-2] == 0x74 # LOAD_GLOBAL 9 ci = code.co_code[bi-1] 10 name = code.co_names[ci] 11 print(name) 12 except: 13 print('予想外です!') 14 15zero = one = num_test 16zero() # ==> zero 17one() # ==> one 18 19def main(): 20 local_one = one 21 local_one() # ==> 予想外です! 22 23main()

(CPython 3.7で確認)

上のようなコードはインタープリタの内部仕様に依存しすぎているので、デバッグ目的でのみ用いるのが無難ではないでしょうか?

現実的な方法

現実的にはzeroやoneなどの値を異なる関数インスタンスにするのがもっとも素直な方法だと思います。

Python

1zero, one, two = ((lambda x: lambda: x)(v) for v in range(3)) 2 3# もちょっと分解して書くなら 4 5def num_function(value): # 関数を返す関数 6 def dummy(): 7 return value 8 return dummy 9 10zero, one, two = (num_function(v) for v in range(3)) 11 12# もっとたくさんの識別子の定義が必要なら 13 14names = ['zero', 'one', 'two', ..., 'one_hundred'] 15for i, name in enumerate(names): 16 globals()[name] = num_function(i) # これはひどい...

この実装はもはや質問者さんの意図とは離れたものですが、先に書いた怪しげな実装に比べ、単純明快です。

投稿2018/11/19 16:16

編集2018/11/27 01:37
KSwordOfHaste

総合スコア18394

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

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

退会済みユーザー

退会済みユーザー

2018/11/20 03:31

回答ありがとうございます。やはり別インスタンスにしないと無理そうですね。
KSwordOfHaste

2018/11/20 04:13

その方がよさそうです。sys.argv[0]を見て動作を変えるスクリプトなんてのもよくありそうなので本件のアイデアは面白いと思うのですが、いかんせんPythonの関数呼び出しのレベルでそれをやるのは少々厳しそうですね。
guest

0

解決策ではなく、できませんでした報告です。

とりあえず改行すればできるんじゃないかと思ったのですが、

python

1import inspect 2 3def f(): 4 s = inspect.stack() 5 print(s[1].code_context) 6 return -1 7 8one = two = three = f 9print(one(), 10 two(), 11 three()) 12print(one() + 13 two() + 14 three()) 15 16""" => 17['print(one(),\n'] 18[' two(),\n'] 19[' three())\n'] 20-1 -1 -1 21[' two() + \n'] 22[' two() + \n'] 23[' three())\n'] 24-3 25"""

上はまあまあですが、下は期待通り動きません。しばらく考えた結果、演算子だとインタプリタスタックが期待通り積み上がらないのだろうと推測しました(深くは追求していません)。

となると、かなり難しいんじゃないかなぁという気がします。


期待通りではないかもしれませんが、

python

1zero = one = two = three = num 2magic() # ここに一行入れて良いならなんでもし放題ではある 3print(one() + two() + three())

方法は色々思いつきます(globals()を書き換えるのは前提として、情報を持たせられるクラスのインスタンス(当然__call__も定義)にすげ替えるとか)。

投稿2018/11/19 13:53

編集2018/11/19 14:02
hayataka2049

総合スコア30933

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

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

KSwordOfHaste

2018/11/20 01:20 編集

code_contextはスタックフレームにあるbyte code位置(f_lasti)をcodeインスタンスが保持している行番号情報(co_lnotab)から検索して行番号を特定し生成していると思いますが、co_lnotabを見ると (exp1 + exp2 + ... expN) みたいなコードはexp1~expN-1のbyte codeが全部exp2の行にあると記録されてました。 実際のソースコードの行になるべく一致するようになっていた方がスタックトレースを見たとき混乱しなくて済むと思うのですが、行番号情報はあくまでデバッグ情報でしかないためbyte code compilerの実装がおざなり(?)になっているだけのような気もします。ただpythonのコードって複数行にわたることがほぼないのでむしろ「この程度の精度で充分でありこれ以上綿密な実装はオーバースペック」という判断なのかも知れません。 ただ、デバッグの際「実際の実行ラインがスタックトレースに出ているより後の可能性がある」という知識が役立つことが(いつか)くるかもです・・・
退会済みユーザー

退会済みユーザー

2018/11/20 03:19

回答ありがとうございます。inspect.stack()からのアプローチでは無理そうなことが分かっただけでも大収穫です。
hayataka2049

2018/11/20 11:08

>KSwordOfHasteさん 私も気づいたときはひどい実装だと思いましたが、演算子でまでデバッグ情報を拾うオーバーヘッドや、メンテナンスの手間を考えると妥当かもしれないと考え直しました。 >kichirb3さん そう言っていただけると嬉しいです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問