モジュールとインポート
自分で書いたファイルを別のファイルから使うこと、インポートが実際に何をするのか、`if __name__ == "__main__"` が必要な理由、標準ライブラリ、そして自分のファイルでモジュールを隠してしまう罠。
どうやって学びますか?
解決する問題
前の章では line_total 関数を書きました。今度は、2つ目のプログラムが同じものを必要としています。
いちばん簡単な方法は、そのコードをコピーすることです。最初の日はそれでうまくいきます。
ところがそのあと税率が変わります。1つのファイルを直して、もう1つのファイルを直し忘れます。すると2つのプログラムが、同じ注文に対して違う合計を表示するようになります。どちらが正しいかを決める方法がありません — どちらも、自分のファイルの中では正しいからです。
問題はコピーする手間ではありません。問題は、真実が2か所に存在してしまうことです。
import pricing
print(pricing.TAX_RATE)
print(pricing.line_total(15.0, 3))0.15
51.751つのファイルを作れば、それをいくつのプログラムからでも使えます。税率が変わったら、変更するのは1か所だけです。
この章では、その1行 — import — と、その裏で実際に何が起きるのかを扱います。
この章を終える頃には
- 自分で書いたファイルを、別のファイルからインポートできる
import x、from x import y、import x as zの違いを説明できる- インポートの瞬間に何が起きるのか、そしてそれが何回起きるのかを説明できる
- なぜ
if __name__ == "__main__":と書くのかを説明できる - 標準ライブラリからモジュールを取り込める
- Python がどこを探すのかを知り、自分のファイルでモジュールを隠してしまうことを避けられる
前提条件: スコープとミュータブルなデフォルト値。
モジュールはただのファイル
pricing.py という名前のファイルは、pricing という名前のモジュールです。何かを宣言する必要も、特別な行を書く必要もありません — ファイルがあれば十分です。
import pricing はモジュール全体を取り込み、中の要素には、ドットを使ってアクセスします:pricing.TAX_RATE、pricing.line_total(...)。
ドットは余計な入力に見えるかもしれませんが、役割があります — 読むときに、その要素がどこから来たのかを示してくれるのです。大きなファイルではこれがとても役立ちます。
必要なものだけを取り込む
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)51.75
NameError: name 'TAX_RATE' is not definedfrom ... import ... は、指定された名前だけを取り込みます。そして、その名前はそのまま使われます — ドットは要りません。
(エラーの行の前にも Python はトレースバックの行をいくつか表示しますが、この章では通常、最後の行だけを示します。)
ただし、ほかの名前は取り込まれません。TAX_RATE は指定されていないので、同じファイルにあっても、ここでは使えません。
どちらを使うべきか? おおまかに言うと、名前が2つか3つ必要なら from、たくさん必要ならモジュール全体です。そして1つだけ、絶対にしてはいけないことがあります — from pricing import * です。これはすべての名前を黙って取り込んでしまうので、そのあとで line_total がどこから来たのかを調べる方法がなくなります。
取り込むときに名前を変える
import pricing as p
print(p.line_total(15.0, 3))51.75as は、このファイルの中でモジュールに短い名前を付けます。
名前が本当に長いときや、よく使われる慣習がある場合にだけ使いましょう。面倒だからといって1文字の名前にすると、ドットが与えていた読みやすさを失ってしまいます — p.line_total(...) を読んでも、どこから来たのか誰にも分かりません。
インポートはファイルを実行すること
これは多くの人があとになって気づく部分で、たいてい都合の悪いタイミングで気づきます。
tools.py:
print("tools is being set up")
def ping():
return "pong"main.py:
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")main starts
tools is being set up
pong
main ends注目すべき点が2つあります。
インポートはファイルを実行することです。上から下まで順に。 def の行は関数を作ります。それ以外の行 — たとえばあの print — は実際に実行されます。モジュールの最上位に print や input を置くと、それをインポートした人にそのまま影響します。
2つ目の import tools は何もしませんでした。 モジュールはプログラムの中で1回だけ実行されます。そのあとは、Python がすでに持っているものを返すだけです。だから、同じモジュールを10か所でインポートしても、安全で負担もほとんどありません。
__name__ と、あの有名な1行
すべてのモジュールの中には __name__ という名前があり、これは Python が自動的に設定します。
tools.py:
def ping():
return "pong"
print("module name is", __name__)
if __name__ == "__main__":
print("running directly")python tools.py として実行すると:
module name is __main__
running directly一方、別のファイルから import tools すると:
module name is tools今回は running directly の行が表示されませんでした。条件が偽だったからです。
ルールは単純です:実行しているファイルは __name__ が "__main__" になり、インポートされるファイルは自分の名前を持ちます。
つまり、if __name__ == "__main__": の中身は、そのファイルが直接実行された場合にだけ動き、インポートされたときには決して動きません。
なぜそれが必要なのかは、前のセクションから導かれます。インポートはファイルを実行することです — だから、ファイルの最後に main() があると、それをインポートした人はうっかりあなたのプログラムまで実行してしまいます。あの1行が、その扉を閉めるのです。
標準ライブラリ
非常に多くのモジュールは Python 自体に同梱されています。何かをインストールする必要はありません。
import math
import statistics
print(math.sqrt(144))
print(math.floor(3.7), math.ceil(3.2))
print(statistics.mean([10, 20, 60]))12.0
3 4
30もう1つ、よく登場するものがあります:
import random
random.seed(7)
print(random.randint(1, 100))
print(random.choice(["pen", "bag", "ink"]))42
penrandom.seed(7) がなければ、実行するたびに違う数字が出るでしょう — それが乱数というものですから。シードを設定すると数列が固定されるので、同じ Python のバージョンであれば、上の出力があなたのマシンでも同じになります。テストを書くときにはこれがとても重要です。ランダムなものをテストするには、まず再現可能にしなければならないからです。
これは「ランダム」ではなく、「ランダムに見える」だけです。randomは計算によって数字を作るので、同じシードからは常に同じ数列が生まれます。パスワードやトークンには使わないでください — そのためにsecretsモジュールがあります。
Python が探す場所
import pricing のとき、Python は順番に探します:まずインタープリターに組み込まれたモジュール、次に実行中のスクリプトのフォルダ、次に標準ライブラリ、最後にインストール済みのパッケージです。
この順番には結果があり、それに引っかかる人は少なくありません。
フォルダの中に random.py というファイルがあり、こう書いたとします:
import random
print(random.choice([1, 2, 3]))AttributeError: module 'random' has no attribute 'choice'自分のファイルが本物の random より先に見つかったので、インポートされたのは自分のファイルです — そしてその中には choice がありません。
このメッセージは分かりにくいです。random に choice がない、と言っていますが、それは不可能に聞こえるからです。しかし、ここで言っているのはあなたの random のことです。
ルール:ファイルにモジュールと同じ名前を付けてはいけません。 random.py、string.py、json.py、csv.py — どれも無害に見える名前です。だからこそ、この罠は効くのです。
例外が1つあり、それがこのルールの理由を説明します。 多くのインストール環境では(たとえば、この章の例を実行した Linux システムでは)、math.pyというファイルを作ってimport mathしても、自分のファイルではなく本物が読み込まれます。mathはインタープリターに組み込まれていて、組み込みモジュールは検索順序の先頭に来るからです。どのモジュールが組み込まれているかは、Python のビルド方法によって決まります。つまり罠が発動する名前と、しない名前があり、それは単にファイルの名前を変えるよりも覚えにくいのです。
フォルダにファイルを整理する
ファイルが増えてきたら、フォルダにまとめられます。フォルダの中に __init__.py ファイルを置きます — 空でかまいません — するとそのフォルダはパッケージになります。
shop/
__init__.py
pricing.py
main.pyパスはドットを使って書けるようになります:
from shop.pricing import line_total
print(line_total(15.0, 3))51.75今はこれで十分です。この章の残りでは、フラットなファイルのままにしておきます。
完全な例
3つのファイルです。依存関係は一方向だけに流れます。
pricing.py:
"""Prices and tax. Nothing here prints, and nothing here asks for input."""
TAX_RATE = 0.15
def line_total(price, quantity):
"""One line of an order, tax included."""
return round(price * quantity * (1 + TAX_RATE), 2)
def order_total(lines):
"""`lines` is a list of (name, price, quantity) tuples."""
return round(sum(line_total(price, qty) for _, price, qty in lines), 2)report.py:
"""Turning numbers into lines of text. It imports pricing; pricing imports nothing."""
import pricing
def receipt(lines):
rows = [f"{name:<8} {pricing.line_total(price, qty):>8.2f}"
for name, price, qty in lines]
rows.append("-" * 17)
rows.append(f"{'total':<8} {pricing.order_total(lines):>8.2f}")
return rows
if __name__ == "__main__":
print("report.py has no data of its own to show")main.py:
"""The only file that is meant to be run."""
import statistics
from report import receipt
ORDER = [
("pen", 15.0, 3),
("bag", 850.0, 1),
("ink", 120.0, 2),
]
def main():
for row in receipt(ORDER):
print(row)
print()
quantities = [qty for _, _, qty in ORDER]
print("mean quantity:", statistics.mean(quantities))
if __name__ == "__main__":
main()python main.py として実行すると:
pen 51.75
bag 977.50
ink 276.00
-----------------
total 1305.25
mean quantity: 2注目すべき点が4つあります。
依存関係は一方向に流れます。 main は report を知っており、report は pricing を知っていて、pricing は誰も知りません。この順序は鎖を作り、その末端にあるファイル — pricing — は単独でテストできます。もし pricing が report をインポートする必要があったら、どちらも単独では成り立たなくなっていたでしょう。
pricing.py は何も表示せず、何も入力を求めません。 値を受け取り、値を返すだけです — 第21章の純粋な関数です。インポートはファイルを実行することなので、この控えめさは礼儀ではなく、必須条件なのです。
report.py にも if __name__ == "__main__": がありますが、これは役に立つことをしません — このファイルには独自のデータがないので、表示するものがないのです。省くこともできました。それでも残してあるのは、万一このファイルが直接実行されたときに、何もせず黙っているのではなく、少なくとも何かを言うようにするためです。
main.py 以外のどのファイルも、単独では何もしません。 プログラム全体にはエントリーポイントがちょうど1つだけあり、それを読めば何が起きるかが分かります。
うまくいかないとき
ModuleNotFoundError: No module named 'priceing' 綴りの間違い、またはファイルが別のフォルダにある場合です。実行しているスクリプトと同じフォルダにあるか確認してください。
ImportError: cannot import name 'line_totl' from 'pricing' モジュールは見つかったけれど、中にその名前がありませんでした。メッセージの末尾には、かっこ内にモジュールの完全なファイルパスが表示され、よく Did you mean: 'line_total'? のような近い候補も示されます。
AttributeError: module 'random' has no attribute 'choice' ほとんどの場合、自分のファイルが本物のモジュールを隠しています。フォルダの中でその名前のファイルを探して、名前を変えてください。それで十分です — .py ファイルがなくなれば、__pycache__ に残っていた古いコピーは Python が使わなくなります。
ImportError: cannot import name 'rows' from partially initialized module 'table' (most likely due to a circular import) 2つのファイルが互いにインポートし合っていて、そのうち少なくとも1つが from ... import ... で、相手のファイルにまだ存在しない名前を要求しています — 相手のファイルが上から下まで実行し終わっていないからです。通常の解決方法は、両方が必要とするものを、どちらもインポートしない3つ目のファイルに移すことです。
インポートした瞬間に何かが表示された、または入力を求められた モジュールの最上位に、コードが置かれています。それを関数の中に入れて、if __name__ == "__main__": の中から呼び出してください。
ファイルを編集したのに、古い動作が見えたままです プログラムをもう一度実行してください — 実行中のプログラムはモジュールを再読み込みしません。ノートブックの場合は、カーネルを再起動してください。
ステップ 4 / 6 — 予想する
理解度をチェック
line_total は取り込まれましたが、TAX_RATE は取り込まれていません。何が起きるでしょう?
# pricing.py
TAX_RATE = 0.15
def line_total(price, quantity):
return round(price * quantity * (1 + TAX_RATE), 2)
# main.py
from pricing import line_total
print(line_total(15.0, 3))
print(TAX_RATE)- A`51.75` が表示され、そのあと `None`
- B`51.75` と `0.15` の両方が表示される
- C`51.75` が表示され、そのあと `NameError`
- D`ImportError`
import tools が2回書かれています。何が表示されるでしょう?
# tools.py
print("tools is being set up")
def ping():
return "pong"
# main.py
print("main starts")
import tools
import tools
print(tools.ping())
print("main ends")- Atools is being set up main starts pong main ends
- Bmain starts pong main ends
- Cmain starts tools is being set up pong main ends
- Dmain starts tools is being set up tools is being set up pong main ends
python main.py を実行したとき、何が表示されるでしょう?
# tools.py
print("name:", __name__)
if __name__ == "__main__":
print("direct")
# main.py
import tools
print("done")- Aname: tools done
- Bname: main done
- Cname: __main__ direct done
- Dname: tools direct done
回答にはアカウントが必要です
ログインして答えを確認
問題は上にあります。頭の中で答えを考えることがいちばん大切です。ログインすると、正解・解説・3 段階のヒントを確認できます。
やってみよう
1つのフォルダに、3つのファイルを作りましょう。
textutil.py — 2つの関数:initials(name) は "rafi ahmed" を "RA" に変え、shorten(text, limit) は長いテキストを切り詰めて、末尾に ... を付けます。このファイルの中には print を一切書かないでください。
table.py — textutil をインポートし、rows(people) を提供します。これは名前のリストを受け取り、整形された行のリストを返します。
main.py — table をインポートし、いくつかの名前を渡して呼び出し、表示します。3つのファイルすべてに if __name__ == "__main__": を書いてください。
そのあと、4つの実験を行いましょう。どれも自分の目で確かめる価値があります:
textutil.pyの一番上にprint("loaded")を追加して、python main.pyを実行してください。その行は表示されますか?main.pyの最初のprintの前ですか、後ですか?main.pyにimport tableを2回書いてください。loadedは何回表示されるでしょう?- フォルダに空のファイル
statistics.pyを作り、main.pyでstatisticsをインポートしてstatistics.mean([1, 2])を呼び出してください。メッセージをよく読んでください — どのstatisticsの話をしているでしょう? textutil.pyの一番上にfrom table import rowsを追加して、2つのファイルを互いにインポートさせてから、python main.pyを実行してください。メッセージの最後の行は次のように始まります:ImportError: cannot import name 'rows' from partially initialized module 'table'。(ただのimport tableならエラーは出ません。その場合textutilは、途中まで組み立てられたモジュールへの名前を受け取るだけで、rowsは関数が実行されるときに後から探されます。from table import rowsは、table.pyがまだ1行目で止まっている間に、その名前を今要求するのです。)
最後の2つは、メッセージに見覚えがないと数時間を失いかねない間違いです。一度自分で引き起こしてしまえば、数秒で解決できるようになります。