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

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

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

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

Q&A

2回答

989閲覧

ソースファイルの行番号をprintで出力したい

fuku-chann

総合スコア82

Python 3.x

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

0グッド

0クリップ

投稿2022/11/30 09:13

printでソースファイルの行番号を出力することを検討しています。
Terminalの3行目に119を出力したいのですが、107になってしまいます。
まだまだ勉強不足と思いますが、修正点をご教示いただける方おられましたら、お願いいたします。

Python

1import inspect 2 3def location(depth=0): 4 # frame = inspect.currentframe(depth+1) 5 frame = inspect.currentframe() 6 return (frame.f_code.co_filename, frame.f_lineno) 7 8# if __name__ == '__main__': 9# def f(): 10# g() 11 12def g(): 13 print(location(1)) 14 # print(location(1)) 15 # print(location(2)) 16 print (117, __file__) 17g() 18print(119, __file__, location())

Terminal

1('test3.py', 107) 2117 test3.py 3119 test3.py ('test3.py', 107)

参考にした記事
inspect --- 活動中のオブジェクトの情報を取得する
Python で現在のファイル名と行番号を調べる。
Pythonのソースファイルの行番号を取得したい

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

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

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

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

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

yuma.inaura

2022/11/30 09:31

ここでいうソースファイルってなんでしょう?
fuku-chann

2022/11/30 09:42

このコード自体があるファイルのことです。
guest

回答2

0

本家ドキュメントは参照されているようですが、難しかったでしょうかね。
ソースの元のにしている2番目のリンクは 2010-04-10 と 12年以上前のものなので、仕様が変っています。
3番目も2016と古いのですが仕様は現在と同じです。 でも、使いかたが異なるのですね。

これを直すのはドキュメントに出てくるフレームオブジェクト(スタックフレーム)の知識が要るのでやっかいですね。

元の関数と同じような処理をする関数を作ってみました。

python

1def location(depth=0): 2 frame = inspect.getouterframes(inspect.currentframe())[depth+1] 3 return (frame.filename, frame.lineno)

これでできると思いますよ。

投稿2022/11/30 09:42

TakaiY

総合スコア12657

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

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

fuku-chann

2022/11/30 10:26

ありがとうございます。 解決いたしましたが、スタックフレーム?の仕様変更があった場合は、どこを見れば良いのでしょうか?
TakaiY

2022/11/30 10:37

多分、スタックフレームの仕様は変更にはならないと思います。 今回変更になったのは、inspectモジュールの仕様ですね。 昔のcurrentframe は、 階層構造になっているフレームの何番目を取るか指定できたのですが、「その場」のものしか取れなくなって(まあ、それが名前に合ってますよね)、別関数(getouterframes)で取れるようになったということです。 そういうのってどうやったら気付くことができるかというと、 - 使うときに本家のドキュメントをちゃんと参照する。引数違いなど。 - モジュールを更新するときに、ドキュメントを確認する。 - 親切なモジュールだと、いきなり変えないで、まずは警告を出してくれたりする。 という感じでしょうか。 また、Webの情報を使う場合は情報の鮮度(物によっては半年でも古い)を気にするようにしたほうがいいですね。
fuku-chann

2022/11/30 10:50

ありがとうございます。 ドキュメントとは下記のことでしょうか? https://docs.python.org/3.12/library/inspect.html?highlight=inspect#module-inspect 上記ページにgetouterframesの記載がありますが、元のコードで使われていましたcurrentframeの記載もあります。今回は具体的にどこをみてご修正されたのでしょうか? inspect.getframeinfo(frame, context=1) Get information about a frame or traceback object. A Traceback object is returned. バージョン 3.11 で変更: A Traceback object is returned instead of a named tuple.
TakaiY

2022/11/30 11:33

ドキュメントはそれです。python.orgのものなので、本家です。 currentframeメソッドには引数がありません。 引用しているものにはあるようですが、多分昔はあったのでしょう。よくあることです。 フレームは階層構造になっているのは知っているので、現在のフレームから、前のフレームを取得するものは無いかなと見てみると、すぐ上に、getinnerframes、 その上に getouterframes があって、読んでたり検索してみるとそれっぽいので使ってみる、といった感じですね。
fuku-chann

2022/11/30 12:11

ありがとうございます。 少しずつイメージが湧いてきました。 フレームが階層構造というのはどういうことでしょうか? 最後になりますが、ご回答お願いいたします。
teamikl

2022/11/30 12:41

昔は sys._getframe のエイリアスだったので、意図しない使われ方をしていたのが修正されました。 https://github.com/python/cpython/commit/42ac47548de06ac4c022e54abdbdc467f80cecb2 getouterframes を使う場合、再起呼び出しで使った場合に不要なリストを沢山作ることになるので、 必要最小限の呼び出し元を辿るように改善できます。 def location(depth=0):  frame = sys._getframe(depth+1)  return (frame.filename, frame.lineno) 因みに行番号の表示は、printの代わりに標準ライブラリのloggingモジュールでの出力がお勧めです。 内部の変更に関わらず、単体テストにより、ライブラリレベルで挙動が保証されてます。
fuku-chann

2022/11/30 22:41

ご提案ありがとうございます。
TakaiY

2022/12/01 05:09

フレームの階層構造については、むずかしい話なので「スタックフレーム コールスタック」で検索すると出くる情報を参照いただければと思います。
TakaiY

2022/12/01 05:10

teamiklさん、有用な情報ありがとうございます。 勉強になりました。
guest

0

実用的な方法ではありませんが、行番号を得る方法の紹介。
コードの実行には python3.9以降を要求。

python

1import ast 2import dis 3import types 4import inspect 5import logging 6import textwrap 7 8logger = logging.getLogger(__name__) 9 10 11## 構文木操作で __lineno__ を行番号に置換する方法 12 13class _ReplaceLineno(ast.NodeTransformer): 14 15 def __init__(self, offset=0): 16 super().__init__() 17 self._offset = offset 18 19 def visit_Name(self, node): 20 if node.id == "__lineno__": 21 return ast.Constant(self._offset + node.lineno) 22 return node 23 24 25def pre(func): 26 offset = func.__code__.co_firstlineno-1 27 src = inspect.getsource(func) 28 src = textwrap.dedent(src) # クラス内メソッドやインナー関数に対応 29 tree = ast.parse(src) 30 tree = ast.fix_missing_locations(_ReplaceLineno(offset).visit(tree)) 31 src = ast.unparse(tree) 32 ctx = {"pre": lambda f:f} 33 exec(src, func.__globals__, ctx) 34 return ctx.get(func.__name__) 35 36 37# ※ 制限: デコレータで処理なので、関数内でないと使えない 38@pre 39def func(): 40 print("static lineno", __lineno__) 41 print(f"static lineno {__lineno__}") 42 43 logger.info(f"HELLO logging reports funcName:lineno") 44 45 46class A: 47 @pre 48 def test(self): 49 print(f"TEST {self} {__lineno__}") 50 51 52 53## 実行時情報 (Frame) から行番号を得る方法 54 55class _GetLineno: 56 def __str__(self) -> str: 57 # ※ 文字列型で返します 58 return str(inspect.currentframe().f_back.f_lineno) 59 60 61__lineno__ = _GetLineno() 62print("dynamic lineno", __lineno__) 63print(f"dynamic lineno {__lineno__}") 64 65 66def main(): 67 func() 68 A().test() 69 70 print("----" * 10) 71 72 # func() 内ではグローバルの __lineno__ が使われていないことを 73 # バイトコードで行番号が埋め込まれているのを確認 LOAD_CONST 74 dis.dis(func) 75 76 # dis.dis(A.test) 77 78 79if __name__ == '__main__': 80 logging.basicConfig( 81 level=logging.DEBUG, 82 # ログ表示に 関数名:行番号 を設定 83 format="%(funcName)s:%(lineno)d %(message)s", 84 ) 85 main() 86

実際の開発では loggingモジュールの利用をお勧めします。

投稿2022/11/30 14:07

teamikl

総合スコア8664

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

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

fuku-chann

2022/11/30 22:46

ご提案ありがとうございます。 せっかくですのでloggingモジュールも検討したいのですが、コードがもう少し短くなると管理しやすくなるので実用性が向上すると思いますが、短いコードで行番号を出すことは可能でしょうか?
teamikl

2022/12/01 04:11

最低限必要なのは、モジュールのimport、ログレコードの設定 logger はなくても loggingの関数でも使えます。 import logging logging.basicConfig(level=logging.DEBUG, format="%(funcName)s:%(lineno)d %(message)s") logging.info("TEST") logger の生成は省けますが、プログラムの構成が複数ファイルになる場合は どのモジュールでのログか分かったほうが良いので 大抵の場合はファイル冒頭で logger = logging.getLogger(__name__) とします。 format に %(name)s 指定でログに出力可能です。 通常は、各ファイルで記述が必要なのは import と getLogger の2行のみ。 ログの設定は、プログラム全体で1度のみ、もしくは外部の設定ファイル等も使えます。
fuku-chann

2022/12/01 09:57

ファイル名が出力されませんが、どこか間違っていますでしょうか? できればパス名とファイル名を両方出せるようでしたら適宜設定できるようにできたら助かります。 import logging logger = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG, format="%(funcName)s:%(lineno)d %(message)s %(name)s") logging.info("TEST") 出力結果: <module>:131 TEST root
teamikl

2022/12/01 10:20

funcName が <module> になるのは、関数外での実行だからですね。 name が root なのは、ログの呼び出し元が logger.info ではなく logging.info の為です。 logger を使わない場合は、デフォルトの root ロガーの名前となります。 logger.info として利用した場合は、getLogger(__name__) で与えた値 (任意の文字列を設定可能) そのファイルが起動スクリプトであれば __name__ の値、モジュール名の"__main__" 外部の import したファイルでのログであればそのモジュール名になります。 「パス・ファイル名」と「パッケージ・モジュール名」との区別はありますが、 ファイル名が必要な場合は %(pathname)s が使えます。 その他、ログで設定可能な項目は、LogRecord 属性の一覧をご覧ください https://docs.python.org/ja/3/library/logging.html#logrecord-attributes
fuku-chann

2022/12/01 11:13

ありがとうございます。 下記のようにメッセージを2つ以上出力することがありますが、printのようにカンマを使うとエラーになります。 import logging logging.basicConfig(level=logging.DEBUG, format="%(pathname)s:%(funcName)s:%(lineno)d %(message)s") logging.info(1, "TEST") 公式ドキュメントにはカンマで出力できているように見えますか、できないのでしょうか? https://docs.python.org/ja/3/library/logging.html
teamikl

2022/12/01 14:53

古い書き方で logger.info("name %d age %d", name, age) の様にフォーマットを指定すれば 引数で渡すことができますが、第一引数は必ず文字列で、引数はフォーマットの数と一致しなければなりません。 後方互換性の為に残されている機能なので、新しいコードでは f-string を使いましょう。 ログメッセージ内で変数を確認したい場合などは f"{name=} {age=}" という書き方が便利です。
fuku-chann

2022/12/01 18:41 編集

すみません、色々調べてたのですが、解決に至りませんでした。 修正してみましたが、下記エラーが出ています。 お手数ですが、ご教示よろしくお願いいたします。 import logging logging.basicConfig(level=logging.DEBUG, format="%(pathname)s:%(funcName)s:%(lineno)d %(message)s %(name) %(age)") logging.info(f"{pathname=}{funcName=}{lineno=}{message=}{name=} {age=}") エラー: File "<fstring>", line 1 (pathname=) ^ SyntaxError: invalid syntax
teamikl

2022/12/02 03:28 編集

SyntaxError が出るということは、もしかして python のバージョンが古い等かな? コードをそのまま実行した場合、私の環境(python3.11)では NameError になります。 他の問題点: 変数はそれぞれ宣言されてますか? logging.info(message) で渡した文字列が format の %(message)s に入ります。 pathname, funcNme, lineno は format で設定してるので logging.info では不要です。 逆に format 側での name, age は message に入る内容なので不要です。 以下のコードを試してみてください。SyntaxError になる場合は python のバージョンを確認 3.6以降が必要です import logging logging.basicConfig(level=logging.DEBUG, format="%(pathname)s:%(funcName)s:%(lineno)d %(message)s") name, age = "TEST", 10 logging.debug(f"{name=} {age=}") f-string が使えない場合は str.format logging.debug("name={} age={}".format(name, age))
teamikl

2022/12/02 04:41

pathname 表示してみたけど、パスが長い場合にログのメッセージが見にくくなりますね。 代替で filename もありますが、別フォルダの同名ファイルを区別も必要なら、name がよく使われます。 名前が __main__ になるのは最初に実行されるファイルのみなので、 複数ファイルでどのファイルでのログかを判別したい場合は name で十分だったりします。 複数ファイルでの動作デモ https://replit.com/@MiKLTea/PythonLogging#main.py
fuku-chann

2022/12/02 08:59 編集

下記で問題なく動きましたのでバージョンの問題かと思われます。 import logging logging.basicConfig(level=logging.DEBUG, format="%(pathname)s:%(funcName)s:%(lineno)d %(message)s") name, age = "TEST", 10 logging.debug("name={} age={}".format(name, age)) バージョンは3.6.0でしたので3.6以降と思いますが、エラーが出ました。f-stringの方がコードが簡潔なのでバージョンアップを検討しています。seleniumやブラウザ(Firefox)との相性も気になりますが、一度試したいと思っております。 12/02/22 17:53 /Users/xxxx python -V Python 3.6.0
teamikl

2022/12/02 09:29

こちらの環境win10/64では 3.6.0 でも動作確認できたので、 実行に使われている python が別のものになっているのかもしれません。 スクリプトを実行して、実際のバージョンを確認して見て下さい。 import sys print(sys.version)
fuku-chann

2022/12/02 09:47

下記のコードでも確認しましたが、バージョンは3.6.0で間違いないと思われます。 こちらはMacOSです。 import sys print(sys.version) 結果: 3.6.0 (default, Jul 15 2020, 23:07:22) [GCC 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.8)]
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問