よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

長くなったpyファイルの分割と、別ファイルや関数の呼び出し方法

pyファイルが長くなると読みにくくなります。「分割できないかなぁ」と思いますが、どうやるんでしたっけ?
いろいろ確認する。



【実行環境】
Android
Termux
Python3.9



目次




まずは基のファイルを準備する


次のように書いたファイルを作ります。

print("hello")

print文だけが書かれたファイル(file_1.py)を作りました。
これを普通に実行してみます。

$ python file_1.py
hello

ここまではいいですね、ただファイルを実行しただけです。



実行用の別ファイルを準備する


別のファイルからさっきのファイルを読み込み実行する準備をします。

先ほど作って実行したfile_1.py同じ階層(同じフォルダ)に、適当にmain.pyと名付けたファイルを新たに作ります。
このmain.pyを実行用にし、file_1.pyを読み込ませます
main.pyには次のように書きます。

import file_1

インポートしただけ。こんなので別ファイルを読み込む。



別ファイルを読み込むには、import ファイル名を書くだけ!簡単!


ファイル名の拡張子「.py」の部分は不要です。
「呼び出すファイル」と、importで「読み込まれるファイル」は同じ階層に置いておくこと。
では、main.pyを実行してみます。

$ python main.py
hello
  • main.pyを実行する

  • main.pyの1行目の「import file_1」が読まれ、

  • file_1.pyが実行される

という流れになりました。

import file_1.pyとするとどうなる?

main.pyの「import file_1」を「import file_1.py」に書き換えて実行してみる。

import file_1.py
$ python main.py
/main.py", line 1, in <module>
    import file_1.py
ModuleNotFoundError: No module named 'file_1.py'; 'file_1' is not a package

「そんなモジュールは無い」「パッケージじゃない」とエラーがでました。
自作のpyファイルをどうにかしてモジュールとして使えるようにすればエラーにならなさそうですが、それについてはまた別の機会に。

main.pyの中身を「import file_1」に書き直し、次に進みます。



呼び出されるファイルを関数にする


呼び出されるファイル(file_1.py)には「print("hello")」の1行だけを書いていましたが、これを関数にします。
関数名は適当に「func1」とします。

def func1():
    print("hello")

関数を定義しました。
file_1.pyは定義しただけで、実行部分を書いていませんので、これを実行しても何も出力されない筈です。
file_1.pyを実行し、確かめます。

$ python file_1.py
なし

はい、何も出力されませんでした。

関数の定義のみが書かれ、実行しても何も出力されないファイルを別ファイルから呼び出しても、当然何も出力されないだろう。main.pyを実行し、確認します。

$ python main.py
なし

はい、予想通り何も出力されませんでした。



別ファイルの関数を呼び出す方法


確認しておくと、現在同じ階層に2つのファイルがあり、中身はそれぞれ、

import file_1
def func1():
    print("hello")

となっている。

main.pyを実行し、file_1.pyの関数func1()を呼び出すことはできるだろうか?
勿論できる!
main.pyを次のように書き換える。

import file_1

file_1.func1()
  • importで別ファイルを読み込み、
  • ファイル名.関数名が実行部分

モジュールをインポートして関数を実行する場合と同じですね。

↓こんなのと一緒

import pandas as pd

pd.read_csv(引数)

インポートしたファイル名.関数名になっている。



じゃあ、書き換えたmain.pyを実行して、関数が呼び出されたか確認します。

$ python main.py
hello

読み込んだ別ファイル(file_1.py)の関数(func1())が実行され、無事「hello」を出力しました。



読み込むファイルに関数実行部分を書いていたらどうなる?


読み込むファイルfile_1.pyを下記のように書き換えてみる。

# 関数の定義
def func1():
    print("hello")

# 関数の実行
func1()

main.pyはそのままにして、

import file_1

file_1.func1()

main.pyを実行してみる。

$ python main.py
hello
hello

2回出力されました!
main.py側の、

  1. import file_1 で読み込まれた時
  2. file_1.func1()で関数呼び出し実行した時

の2回。
ということは、読み込まれるファイル側file_1.pyには、読み込まれた時に実行されないようにしておく必要がある。そうしないと上のように実行が重複してしまいます。



1つの手は、file_1.pyには実行部分func1()を書かないこと。
もうひとつは、次でやるように、if __name__=="__main__":というオマジナイを書くこと。



if __name=="main__": のオマジナイ


色んなプログラムを見ると大抵は、関数を定義した下にこのオマジナイを書いて、その下に実行部分を書いてありますよね。ぶっちゃけよく分からず真似していたりするが、その様に書き直してみましょう。

def func1():
    print("hello")

if __name__=="__main__":
    func1()

読み込まれるファイル(file_1.py)を上記のように書き換えました。
nameとmainの両脇のアンダーバーは4ヵ所とも2つずつ繋げる。
mainの両脇はダブルクォートでもシングルクォートでもどちらでもよい。(文字列を表す)
このファイルを実行すれば、「hello」が出力されます。確認します。

$ python file_1.py
hello

これはいいでしょう。
次にこれを別のファイルから読み込み、関数を実行するとどうなるでしょうか。確認します。main.pyはそのまま。

import file_1

file_1.func1()
$ python main.py
hello

今度は重複せず、1回だけ出力されました!



「if __name=="main__":」とその下に実行部分を書く理由は、

他のファイルからimportで読み込まれたら、「if」以下の実行部分を実行しない為~(チコちゃん



ifの部分は、

もし「__name__」という変数が「"__main__"」ならば、以下を実行する

という意味なのですが、定義した覚えの無い変数「__name__」を出力させてみましょう。
次のように書き換えます。

def func1():
    print("hello")

# 謎の変数の中身を出力
print(__name__)

if __name__=="__main__":
    func1()

main.pyはそのまま。

import file_1

file_1.func1()

file_1.pyを上記のように書き換えたら、

  • 読み込まれるファイルを直接実行
  • 呼び出す用のファイルから実行

の2種類で実行してみます。


file_1.pyを直接実行

$ python file_1.py
__main__
hello



main.pyから呼び出す実行

$ python main.py
file_1
hello
  • $ python file_1.py で直接実行の場合
    • name = __main__
  • $ python main.py で呼び出した場合
    • name = file_1

になった。
つまり、__name__という変数が、

  1. 直接実行されると__main__という文字列に置換され、
  2. インポートされるとそのモジュール名(ファイル名)自身に置換される

ということのようらしい。うぅ~ん、分かりにくいが、

$ python ファイル名.pyを実行した際のコマンドライン引数[0]を__name__に渡している



のですと。 泥沼にはまりそうなので、リンクを貼ってここはトンズラしよう…


Pythonのif __name__ == '__main__'の意味と使い方 | note.nkmk.me



importで呼び出すファイルの階層が実行ファイルと違うとき


importされるファイル(file_1.py)と、それを呼び出し実行するファイル(main.py)は、同じ階層(フォルダ)に置いておいた。その為main.pyは

import file_1

という書き方をしているが、main.pyと同じ階層にフォルダ(たとえば TestFolder)を作って、その中にfile_1.pyを置くと「import file_1」だけでは読み込まれなくなる。確かめます。

フォルダを作ってfile_1.pyを移動させる

$ mkdir TestFolder
$ mv file_1.py TestFolder
$ tree
.
├── TestFolder
│   └── file_1.py
└── main.py

1 directory, 2 files

file_1.pyをTestFolderに移動させた。
main.pyを実行する。

$ python main.py
/main.py", line 1, in <module>
    import file_1
ModuleNotFoundError: No module named 'file_1'

モジュールが無くてインポートができない。main.py側のインポート部分を書き換える必要がある。
次のように書き換える。

# インポート 書き換え前→(import file_1)
from TestFolder import file_1

# 実行
file_1.func1()
TestFolder.file_1
hello



または、「from TestFolder import file_1」の代わりに

import TestFolder.file_1

TestFolder.file_1

としても同じ結果になる。
入門書などには、インポートのディレクトリはfromで書く方が良いと書かれてある。「from TestFolder import file_1」の方が分かりやすい。



おわりに


今回はこの辺で。
pip install でインストールしたライブラリのインポートのように自作ファイルを扱うことも可能のようです。それには環境変数の設定とかパッケージの作り方とか、ネット上でバージョン管理や配布したいならPyPIやgitの知識が必要と。
まだまだ先になるな…



以上です。



【追記】
変数と値のみを書いたファイルを呼び出したいときは、たとえば次のようにする。

data_list = ["egg", "apple", "ship", "bird"]

とだけ書いたファイルがあるとすると、呼び出し側は、

import conf

print(conf.data_list)


main.pyを実行する

$ python main.py

これでターミナルにconf.pyに書いたリストが出力される。