Last Updated: 2/6/2024, 5:44:57 AM

# import 文ってなに?

新しい機能を
使えるようにしてくれます。

例えば平方根の計算 sqrt は、最初から使えることはできません。 そのため math という機能を import して使えるようにします。

import math
print(math.sqrt(2))
>>> print(math.sqrt(2))
1.4142135623730951
>>> 

簡単な書式は以下の通りです。 ここではモジュールとパッケージを合わせてライブラリと呼ぶことにします。

email (opens new window) は メールを送信する時に使ったりする標準ライブラリです。 知らなくても大丈夫です。

#
# 絶対 import の例
#     対話モード >>> に
#     コピペで実行できます。
#

# 1) import ライブラリへのパス
import email.mime.text
email.mime.text.MIMEText

# 2) from ライブラリへのパス import グローバル変数
from email.mime.text import MIMEText
MIMEText

# 3) from ライブラリのディレクトリへのパス import ライブラリの名前
from email.mime import text
text.MIMEText

相対 import の例は、パッとは出すことが難しいです。 フォルダ/ディレクトリを作成しないといけないので手間だからです。 よく相対 import を使ってハマっている方をネットで見かけます。

後半に相対 import についても説明はさせていただきますが、 すこし混みいっています。 そのため  相対 import は無理して学んだり、使わなくてもいい  かなと思います。

必要になったタイミングで、相対 import を調べて使えばいいかなと思っています。 PEP 8 でさえ絶対 import を使うように勧められています。

Python コードのスタイルガイド - PEP 8 (opens new window)

絶対import を推奨します。 なぜなら、絶対import の方が通常は読みやすく、 importシステムが正しく設定されなかった(たとえばパッケージ内部のディレクトリが sys.path で終わっていた) 場合でも、 より良い振る舞いをする(または少なくともより良いエラーメッセージを出す)からです:

import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

それでは、いつ使ってもいいのでしょうか? それも PEP 8 に書かれています。

Python コードのスタイルガイド - PEP 8 (opens new window)

しかしながら、明示的に相対importを使うことが許される場合があります。 特に絶対importを使うと不必要に冗長になる複雑なパッケージレイアウトを扱う場合です。:

from . import sibling
from .sibling import example

標準ライブラリのコードは複雑なパッケージレイアウトを避け、 常に絶対importを使うようにすべきです。

標準ライブラリとは pip install したりしなくても、 最初から import できるライブラリのことです。 例えば上で紹介させていただいた email, math は標準ライブラリです。

PEP 8 は、本来は標準ライブラリを書くためのコーディング規約です。 しかし Python を使う人はみんな、なぜか PEP 8 に従っています。 それについては以下で記述させていただきました。 知らなても大丈夫です。

ちなみに Google が定めた Python のコーディング規約もあります。 そこにも相対 import は使わないように書かれています。

相対パスによるインポートはしない。
Google Python スタイルガイド (opens new window)

Google の Python のコーディング規約も PEP 8 と相反するものではなく、 PEP 8 をさらに詳細に煮詰めたような内容になっていて、とても面白いですが、 知らなくても大丈夫です。

# 1. import は難しい

たかがこれだけのことなのですが、エラーに直面したとき、 こんなん簡単だろと思って取り掛かると、 意外と import が複雑なことに気づき凹みます。

import のエラーにぶつかった時のワイ

なんだかんだで、普通に2、3日くらい溶かしてしまいました。 難しい原因を以下に列挙します。

# 1.1. パス

どこから import されているのか、わからない。

# 1.2. スクリプトとモジュール, パッケージ

python sample.py としたときと import sample としたときの挙動の違いが、わからない。

# 1.3. 絶対 import と 相対 import

相対 import はパッケージの中だけでしか使えないことことが、わからない。

# ◯ この記事の目的

import に関するエラーを解消できるようになることです。

Python の import は一見簡単そうで、 エラーに直面すると結構難しかったりします。 import を実行された時に投げられる可能性のある例外は、 以下の4つです。

  1. ModuleNotFoundError
  2. AttributeError
  3. ImportError
  4. ValueError

例外ごとに対応する方法をまとめたものを ページの最後にまとめました。 ただ、いきなり見ても、ちょっと難しいかなと思います。

# ◯ 誰のために書かれたの?

おそらく、このページにこられた方のほとんどが import から例外を投げられて、その対応を調べるために こられた方だと思います。

できれば、例外の対応だけを先に示す、という書き方がしたかったのですが。 結論だけ先に述べて最短で問題を解決するという書き方が、できませんでした。

なぜなら import は「パス」に関する挙動が、すこし複雑だからです。 煩雑ではありますが、どうしても手を動かさないと、 理解しにくいところがあるかなと感じています。

そのため、この記事は、すこしずつ手を動かして import の挙動を理解するような構成になっています。

# 2. なんで import するの?

答え: 全部 import すると重いから

そもそも、なんで import とわざわざ書かないといけないのでしょうか? わざわざ import って書くのは面倒です。 それに import ってファイルの頭に書いてあるとコードが汚く見えます。

それは、重くなるからだと 思っています。 なぜ重くなるかというと import したときに Python のスクリプトが実行されているからです。

例えば、次のような sample.py ファイルを作ると

# sample.py
print('Hello, world!')

import することができます。 そして import した時に 'Hello, world!' が表示されていることがわかります。 import するとスクリプトが実行されていることがわかります。

$ python
>>>
>>> import sample
Hello, world!
>>>

つまり Python を起動した時に 標準ライブラリ (opens new window) や pip install したライブラリを問答無用で 全部 import してしまうと、その分だけスクリプトが実行されてしまうということです。

ちなみに2回 import しても実行はされません。

$ python
>>>
>>> import sample
Hello, world!
>>>
>>> import sample
>>> # なにも起こらない

これらの実行文は、インポート文の中で 最初に モジュール名が見つかったときにだけ実行されます。
6. モジュール (module) - Python チュートリアル (opens new window)

import は重いんやでっていうことについて言及してる文章を紹介します。 こちらの文章は日本人の Python コア開発者の id:methane (opens new window) 様の文章です。 日本人にもコア開発者の方がいたんですね! 知った時は感動しました。

起動時間の7割は、 importするモジュールのグローバルのPythonコードの実行。 だから、理想論を言えば、 実行速度を速くしたらついでに起動も速くなった!が最高。
起動高速化 - CPython Note (opens new window)

こちらは Web アプリケーションフレームワーク Flask の生みの親である イケメン Armin Ronacher (opens new window) の文章です。

Python ではインタープリタの起動は非常に重たい処理です。 Python の実行ファイルを起動すると、なんでも実行してくれる大きな装置が呼び出されます。 この大きな装置は、組込型を起動して、import 機構をセットアップして、必要なモジュールを import して、 OS と協調してシグナルを処理しコマンドライン引数を受け取って、内部状態をセットアップしたりします、 この他にも色々と実行しています。 それが最終的に終わると、Python のインタープリタは、あなたのコードを走らせて、シャットダウンします。 これは Python は今日に至る 25 年間もの間やっていたことです。
私が見たい Python - The Python I would like to see (opens new window)

# 3. import の仕組み

import すると、どのような処理が走っているのでしょうか? それは次のようなものです。

  1. パスを検索する。
  2. スクリプトを実行する。
  3. グローバル変数を束縛する。

ちょっと複雑ですが、2 > 3 > 1 の順番で説明します。

# Step 2. スクリプトを実行する。

実は  import すると Python のスクリプトが、裏でひっそりと実行さています。  逆に言えば自分で作った Python のスクリプトは import することができます。

さっそく module.py を作り...

# module.py
variable = 1
print(variable)

これを import してみました。 print(variable) が実行されて 1 が表示されています。

import module
$ python
>>> import module
1  <-------------- 1 が表示されました。
>>> 

# Step 3. グローバル変数を代入する。

 「変数」、「関数」、「クラス」が「モジュールオブジェクト」の属性に代入されます。 

import module
module
module.variable

>>> import module
>>> module  <------------ これはモジュールオブジェクト
<module 'module' from ... >
>>> module.variable <---- module.py のグローバル変数
1
>>> 

より正確には「グローバル変数」が代入されます。 「グローバル変数」とは、モジュールの直下で定義した変数です。 「ローカル変数」とは、関数定義文、クラス定義文の直下で定義した変数です。

var = 1

def f():
    pass

class C:
    pass

var        # グローバル変数
f          # グローバル変数
C          # グローバル変数
def f():
    x = 1  # ローカル変数

class C:
    y = 2  # ローカル変数(クラス変数)

「グローバル変数」、「ローカル変数」については、 この先の以下の記事で説明させていただきます。 読んでいただかなくても大丈夫です。

# Step 1. パスを検索する。

やっと、最初のステップに戻ってきました。 標準ライブラリにオブジェクトをコピーしてくれる copy があります。

import copy

class User:
    def __init__(self, name):
        self.name = name

user_a = User('サーバルちゃん')
user_b = user_a
user_c = copy.copy(user_a)

user_b.name = 'かばんちゃん'
user_c.name = 'ラッキービースト'

user_a.name
user_b.name
user_c.name
>>> user_a.name
'かばんちゃん'
>>> user_b.name
'かばんちゃん'
>>> user_c.name
'ラッキービースト'
>>> 

import すると Python のコードが実行されます。 ということは copy.py がどこかにあるはずです。 では copy.py は、どこにあるのでしょうか?

特殊属性 __file__ を使うと確認することができます。 実際にエディタでファイルを開くとソースコードを確認できます。 結果はお使いのパソコンごとに異なります。

import copy
copy.__file__
>>> copy.__file__
'/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/copy.py'
>>> 

ちなみに user_b.name に代入したら user_a.name も変わってしまいました。 コピーと代入は違います。

そのことについては、以下の記事で説明させていただきました。 特に、読んでいただかなくても、この先に進めます。

# ◯ まとめ

「検索」して「実行」して「代入」する。 これが import の大体の流れになります。

import 文は 2 つの処理を連続して行っています; ある名前のモジュールを探し、その検索結果をローカルスコープの名前に束縛します。
5. インポートシステム - Python 言語リファレンス (opens new window)

# 4. どこを検索しているの?

copy.py はどこにあるかは __file__ を調べれば分かりました。 import はどこを検索しているのでしょうか?

ざっくり言うと、以下の4つのディレクトリを検索しています。

  1. スクリプトのあるパスまたはカレントディレクトリ
  2. 標準ライブラリのコード(Python で書かれた)
  3. 標準ライブラリのコード(C 言語で書かれた)
  4. pip install したコード

具体的に言うと、 検索対象となるパスは sys.path に保存されています。

sys.path (opens new window)
モジュールを検索するパスを示す文字列のリスト。

対話モード >>> に、 以下のコードをコピペして実行して見てください。

import sys
print(*sys.path, sep='\n')

>>> import sys
>>> print(*sys.path, sep='\n')

/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python37.zip
/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7
/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload
/usr/local/lib/python3.7/site-packages
>>> 

インタプリタが命令を端末 (tty) やコマンドプロンプトから読み取っている場合、 インタプリタは 対話モード (interactive mode) で動作しているといいます。
2.1.2. 対話モード - Python チュートリアル (opens new window)

こういう意味不明な長い文字列を見せられると辟易とします。 さらに問題なのは、お使いのパソコンによって、表示されるパスは異なるということです。

そのため、実際に手を動かしていただきながら、 どれに該当するのかを調べていただくと理解が速いかなと感じております。

何か皆様の手間を取らせない良い逃げ方ができないか、 いくらか考えてみたのですが、パッと思いつきませんでした...

以下、個々のものについてご説明させていただきます。 それでは、実際に sys.path を表示させて、 覗いていきたいと思います。

# 4.1. スクリプトのあるパスまたはカレントディレクトリ

1つ目のパスを特定する時から、早速難題です。

実は python sample.py と実行した時と、 import sample と import した時で、 sys.path に追加されるパスが異なるのです。

それぞれの場合について見ていきます。

# 4.1.1. 情報工学実験 第一a 対話モードから実行してみる。

# 4.1.1.1. 実行

以下のコードを対話モードにコピペして実行して見てください。

import sys
print(*sys.path, sep='\n')


# 4.1.1.2. 確認

空っぽの行があります。これはなんでしょうか?

$ python
>>> import sys
>>> print(*sys.path, sep='\n')
        <--------- 空っぽ
/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python37.zip
/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7
/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload
/usr/local/lib/python3.7/site-packages
$

空文字はカレントディレクトリという意味らしいです。 カレントディレクトリとは、python コマンドを押下した時のディレクトリです。

sys.path (opens new window)
スクリプトのディレクトリがない (インタプリタが対話セッションで起動された時や、 スクリプトを標準入力から読み込んだ場合など) 場合、 path[0] は空文字列となり、  Python はカレントディレクトリからモジュールの検索を開始します。 

# 4.1.2. 情報工学実験 第一b コマンドラインから実行してみる。

コマンドライン (opens new window) から実行とは、 次のように実行することです。

$ python package/module.py
# 4.1.2.1. 作成

ご面倒ではありますが   package というディレクトリ/フォルダを作り、その配下に以下のような module.py という Python のコードを保存してください。  import の理解には、どうしても手を動かさないと厳しいところがあるかなと感じています。

# ~/package/module.py
import sys
print(*sys.path, sep='\n')



# 4.1.2.2. 実行

package/module.py をコマンドプロンプトから実行します。 今度は文字列が表示されました。

~/ $ python package/module.py 
/Users/ユーザ名/package  <------- module.py が存在するディレクトリ
/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python37.zip
/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7
/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload
/usr/local/lib/python3.7/site-packages
$


# 4.1.2.3. 確認

「module.py が存在するディレクトリ/フォルダへのパス」が、一番上に表示されることを確認してください。 どの環境でも、このような動作になる筈です。

sys.path (opens new window)
起動時に初期化された後、リストの先頭 (path[0]) には  Python インタプリタを起動したスクリプトのあるディレクトリが挿入されます。 



# 4.1.3. 問題

問題

対話モードから import するのではなく、 コマンドプロンプトから directory ディレクトリ配下にある Python のスクリプト sample.py を実行したとします。

$ python directory/sample.py

このとき sys.path 追加されるのは、カレントディレクトリですか? それとも sample.py が存在するディレクトリですか?

$ # 1) カレントディレクトリ
$ pwd
/Users/lain
$
$ # 2) sample.py が存在するディレクトリ
$ ls directory/sample.py
directory/sample.py
$
  • カレントディレクトリ
  • sample.py が存在するディレクトリ

# 4.1.4. まとめ

コマンドプロンプトから実行した場合と、 対話モードから実行した場合で、追加されるパスが変化します。

sys.path (opens new window)

モジュールを検索するパスを示す文字列のリスト。 PYTHONPATH (opens new window) 環境変数と、 インストール時に指定したデフォルトパスで初期化されます。

起動時に初期化された後、リストの先頭 (path[0]) には  Python インタプリタを起動したスクリプトのあるディレクトリが挿入されます。 

スクリプトのディレクトリがない (インタプリタが対話セッションで起動された時や、 スクリプトを標準入力から読み込んだ場合など) 場合、 path[0] は空文字列となり、  Python はカレントディレクトリからモジュールの検索を開始します。 

スクリプトディレクトリは、 PYTHONPATH で指定したディレクトリの 前 に挿入されますので注意が必要です。

PYTHONPATHとは - Stackoverflow (opens new window)
Pythonが import 文で利用するモジュールを探す際のパスです。 以下のコードで、起動時にデフォルト設定されているパスが確認できます。

$ python
>>> import sys
>>> print(sys.path)
環境変数PYTHONPATHを設定すると、この値に追加されます。

# 4.2. Python で書かれた標準ライブラリへのパス

これが2つ目の難題です。

import すれば使えるものを「ライブラリ」と言います。 これは Python の公式ドキュメントの中に記載のある言葉ではありませんが、 基本的にはそのような使い方をしていいかなと思います。

ライブラリの中でも pip install しなくても 最初から使えるライブラリを「標準ライブラリ」と言います。 copy や math は pip install しなくても使えるので「標準ライブラリ」です。

標準ライブラリには2種類あり Python で書かれたものと C 言語で書かれたものがあります。 以下に GitHub のリポジトリをご紹介します。

# 4.2.1. 情報工学実験 第二

 sys.path で表示されたディレクトリへの一覧の中で、copy.py が保存されているディレクトリを探してください。 

自分の環境では次のパスが該当しました。

/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7

# 4.3. C 言語で書かれた標準ライブラリへのパス

Python のライブラリなのに C 言語で書かれてるってなんだよ?って感じですが、 いまはそんなものもあるんだなくらいに押さえておいてください。

標準ライブラリ math は C 言語で記述されています。

# 4.3.1. 情報工学実験 第三

 sys.path で表示さしたディレクトリへの一覧の中から、math が保存されているディレクトリを探してください。 

自分の環境では次のパスが該当しました。 全く同じものがあったわけではないのですが math.cpython-37m-darwin.so というそれらしいファイルがありました。

/usr/local/Cellar/python/3.7.1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload

so 拡張子は、以下の記事で、すこしだけ勉強しました。

# 4.4. pip でインストールしたライブラリへのパス

最初からはいっている標準ライブラリ以外にも pip でインストールしたライブラリが、 どこかにある筈です。 ちゃんとしたドキュメントを、まだ見つけられていないのですが、 大抵の場合 site-packages という名前のディレクトリに保存されているはずです。

# 4.4.1. 情報工学実験 第四

# 4.4.1.1. 一覧の表示

pip freeze した結果と照らしわせて見てください。

$ pip freeze
pycodestyle==2.4.0
pydocstyle==2.1.1
pyflakes==2.0.0
virtualenv==16.0.0
virtualenv-clone==0.3.0
virtualenvwrapper==4.8.2
$

自分の環境では次のパスが該当しました。

/usr/local/lib/python3.7/site-packages

# 5. モジュールとパッケージ

import できるものは大きくわけて2つ。 「モジュール」と「パッケージ」に分けられます。 それぞれについて見ていきます。

# 5.1. モジュール

Python のファイルはモジュールと呼ばれます。 例えば以下の sample.py は、モジュールです。

ひとつのファイルだけに Python のコードを書き続けると膨大になるので、 モジュールを使って書き分けます。

モジュールの中で定義された変数, 関数, クラスは、 モジュールの属性として参照することができます。

このコードは実行していただく必要はなく、 ざっくり眺めていただければと思います。

# sample.py

# 変数
a = 0

# 関数
def f():
    pass

# クラス
clas C(object):
    pass
import sample

sample.a
sample.f
sample.C
>>> sample.a
0
>>> sample.f
<function f at 0x1010441e0>
>>> sample.C
<class 'sample.C'>
>>> 

module - 用語集 (opens new window)
(モジュール) Python コードの組織単位としてはたらくオブジェクトです。 モジュールは任意の Python オブジェクトを含む名前空間を持ちます。 モジュールは importing (opens new window) の 処理によって Python に読み込まれます。

# ◯ smtblib モジュール

例えばこれは標準ライブラリ smtplib のモジュールです。 詳細を見る必要はまったくないので、 こんなのもあるんだなくらいに思っておいてください。

問題なく import できます。

import smtplib
$ python
>>>
>>> import smtplib
>>>

繰り返しになりますが cpython/Lib/ には標準ライブラリの Python のコードがあります。

# 5.2. パッケージ

モジュールを組み合わせたものをパッケージと呼びます。 複数の Python のファイルを、1つのディレクトリにまとめるとパッケージになります。

package (opens new window)
(パッケージ) サブモジュールや再帰的にサブパッケージを含むことの出来る module のことです。 専門的には、パッケージは __path__ 属性を持つ Python オブジェクトです。

# ◯ email パッケージ

例えば、これは標準ライブラリ email のパッケージ構成です。
email というパッケージの中に、さらに mime というパッケージがあります。 また email も mime も __init__.py というモジュールを持っています。 これは何者でしょうか?

email ---- mime ------------------ __init__.py
       |                       |-- application.py
       |                       |-- ...
       |                       |-- text.py
       |
       |-- __init__.py
       |-- _encoded_words.py
       |-- ...
       |-- utils.py

__init__.py は import したときに、実行されるモジュールです。 import email した後に、email.mime を参照すると AttributeError になります。

import email
email.mime
>>> email.mime
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'email' has no attribute 'mime'
>>> 

なぜ email パッケージ配下のパッケージなのになんででしょうか? それは email/__init__.py の中で mime が import されなかったからです。 逆に言えば email/__init__.py の中で mime を import してくれていれば、 email.mime は AttributeError にはならないということです。

この場合は、おとなしく import email.mime と書かないといけません。

import email.mime

# 6. __init__.py

email パッケージにも、その配下の mime パッケージにも __init__.py がありました。 これは何者でしょうか?

# 6.1. 配下のパッケージは、自動的に import されない。

再び email 配下の、さらに mime の配下の、text を参照したいとします。 このとき email だけ import してから text を参照しようとすると AttributeError になります。

import email
email.mime.text  # AttributeError
>>> import email
>>> email.mime.text
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'email' has no attribute 'mime'
>>> 

正しくは次のように書きます。

import email.mime.text
email.mime.text
>>> import email.mime.text
>>> email.mime.text
<module 'email.mime.text' from '...'>
>>> 

# 6.2. __init__.py

このようなことが何故起こるのでしょうか? それは text が import されていないからです。

email パッケージを import しても email パッケージ配下のモジュールやパッケージが、 自動的に import されて使えるようになるわけでは無いということです。 mime は __init__.py の中で import されていないのです。

ではどうすれば import できるのでしょうか? このときに利用するのが __init__.py です。 email パッケージを import したときに __init__.py だけが実行されているからです。

通常のパッケージがインポートされたとき、 この __init__.py ファイルが暗黙的に実行され、 それで定義しているオブジェクトがパッケージ名前空間にある名前に束縛されます。
5. インポートシステム - Python 言語リファレンス (opens new window)

またサブパッケージを import すると自動的に、その先祖のパッケージの __init__ も自動的に実行されます。

例えば、以下のようなファイルシステム配置は、 3 つのサブパッケージを持つ最上位の parent パッケージを定義します:

parent/
    __init__.py
    one/
        __init__.py
    two/
        __init__.py
    three/
        __init__.py

parent.one をインポートすると暗黙的に parent/__init__.py と parent/one/__init__.py が実行されます。 その後に parent.two もしくは parent.three をインポートすると、 それぞれ parent/two/__init__.py や parent/three/__init__.py が実行されます

Python 言語リファレンス - 5. インポートシステム (opens new window)

# 6.3. 動かして確認してみよう

言葉で説明されてもわからないので、実際に触って動作を確認して見ましょう。

# Step 1. ダウンロード

サンプルコードをご用意させていただきました。 GitHub から以下のディレクトリをダウンロードしてください。

~/ $ git clone https://github.com/niconico25/import

ホームディレクトリ ~/ 配下に展開されたものと仮定して、 お話を続けさせていただきます。

# Step 2. カレントディレクトリの移動

absolute フォルダ/ディレクトリに移動してください。

~/ $ cd ~/import/absolute
~/import/absolute/ $

# Step 3. Python を起動

Python を起動してください。

~/import/absolute/ $ python

# Step 4. package を import して sub_package を使う。

対話モード >>> で次のコマンドを実行してください。

import package
package.sub_package.module_b

# Step 5. 結果を確認する。

長大ななにかが表示されましたが、これらについては、あとで詳細を説明させていただきます。

いま、ここで確認していただきたいことは、 package.sub_package.module_bAttributeError にならなかったことです。

import mail だけでは email.mime.text と 参照できなかったことを思い出してください。

>>> import package
---

/Users/user/import/absolute/package/__init__.py
package
package
---

/Users/user/import/absolute/package/module_a.py
package
package.module_a
---

/Users/user/import/absolute/package/sub_package/__init__.py
package.sub_package
package.sub_package
---

/Users/user/import/absolute/package/sub_package/module_b.py
package.sub_package
package.sub_package.module_b
>>> 
>>> package.sub_package.module_b  <------ AttributeError にならない。
<module 'package.sub_package.module_b' ... >
>>> 

なぜ import package だけでも、その配下のモジュールを参照できたのでしょうか? それは ./package/__init__.py の中で sub_package を import して、 さらに ./package/sub_package/__init__.py の中でも module_b を import していたからです。

# ~/import/absolute/package/__init__.py
import sys
print('---')
print(sys.path[0])
print(__file__)
print(__package__)
print(__name__)

import package.module_a
import package.sub_package  # <--- いまはここだけ注目してください。

それ以外の表記については、この次の章で説明させていただきます。

# 6.4. まとめ

パッケージは __init__.py の中で import した「モジュール」や、 定義した「変数」、「関数」、「クラス」だけが使えるようになります。

ちなみにここでご紹介させていただいた smtplib モジュールと email パッケージを組み合わせると Gmail を送信するコードが書けます。

簡単なウェブアプリケーションのユーザ登録時の メールアドレスの認証などで試験的に使えたりします。

# 7. __main__.py

import したときには __init__.py が実行されますが、 コマンドラインから起動されたときは __main__.py が実行されます。

$ python sample_package  # __main__.py が実行される。

# 5.1. スクリプトとして起動してみよう

# Step 1. スクリプトを実行

引き続き ~/import/absolute/ 配下にいるものとします。

~/import/absolute/ $ python script

実行結果は次のようになるかと思います。

~/import/absolute/ $ python script
---
script
script/__main__.py

__main__
---
script
script/module_a.py

module_a
---
script
script/sub_package/__init__.py
sub_package
sub_package
---
script
script/sub_package/module_b.py
sub_package
sub_package.module_b
~/import/absolute/ $ python script

前章で >>> import package した時と 今回 $ python script した時の出力結果と比較してみると、 いくつか違うところがあります。

# 5.2. __init__.py と __main__.py の違いを比較する

より正確には「スクリプトとして実行した時と、 パッケージとして import した時の違い」の方が、表現としては正しいかもしれません。

では、違いはなんでしょうか? 恐らく大きく異なるのは「パス」が異なります。 これは「4. どこから import されるの?」の復習になります。

出力結果を見ると全体的に違うのですが、まず注目していただきたいのは 実行時のパス sys.path[0] の結果です。

$ python script した場合は 'script' が出力されました。 これは起動したスクリプトがあるディレクトリが保存されています。

sys.path (opens new window)
起動時に初期化された後、 リストの先頭 (path[0]) には Python インタプリタを起動したスクリプトのあるディレクトリが挿入されます。

それに対して >>> import package した場合は、空の文字列 '' が出力されました。

sys.path (opens new window)
スクリプトのディレクトリがない (インタプリタが対話セッションで起動された時や、 スクリプトを標準入力から読み込んだ場合など) 場合、 path[0] は空文字列となり、  Python はカレントディレクトリからモジュールの検索を開始します。 

「パッケージとして import した時」、 「パス」には ~/import/absolute/ が追加されます。 Python インタープリタを起動したディレクトリのパスが設定されます。 例えば module_b を import する場合は、それぞれ次のようになります。

# ~/import/absolute/package/__init__.py
import package.sub_package.moduel_b

それに対して「スクリプトとして実行した時」、 「パス」には ~/import/absolute/script が追加されます。 スクリプトが置かれているディレクトリのパスが設定されます。 例えば module_b を import する場合は、それぞれ次のようになります。

# ~/import/absolute/package/__main__.py
import sub_package.module_b

そのため、実行時のパスが異なるため import する時の指定の仕方が異なって来ます。

# 5.3. モジュール関連の属性

何を出力しているか簡単にご紹介させていただきます。 全てのモジュールに次のようなコードを書き込んでいます。

import sys
print('---')
print(sys.path[0]) # <-- 追加された検索対象のパス
print(__file__)    # <-- ファイルのパス
print(__package__) # <-- 所属しているパッケージの __name__
print(__name__)    # <-- 所属している最上位のパッケージからの位置

だいたいここから引っ張って来ました。 __file__ は PEP まで入り込まないと説明がなかった。
5.4.4. インポート関連のモジュール属性 (opens new window)

__file__ - PEP 3147 (opens new window)
Python 3 では モジュールを import したとき __file__ 属性は Python ファイルへのパスを指します。 (Python 2 においてはバイトコンパイルされたファイルパスを指します。)
In Python 3, when you import a module, its __file__ attribute points to its source py file (in Python 2, it points to the pyc file).

__package__ (opens new window)
モジュールの __package__ 属性は設定されなければなりません。 値は文字列でなければなりませんが、__name__ と同じ値でも構いません。 モジュールがパッケージの場合、__package__ の値はその __name__ でなければなりません。 モジュールがパッケージでない場合、トップレベルのモジュールでは __package__ 空の文字列、サブモジュールでは親のパッケージ名でなければなりません。 詳細は PEP 366 を参照してください。

__name__ (opens new window)
__name__ 属性はモジュールの完全修飾名に設定されなければなりません。 この名前を利用してインポートシステムでモジュールを一意に識別します。

よく使うこの書き方の __main__ が出力されていますね。 いままで曖昧な理解だったのですが、すこし理解できました。

if __name__ == "__main__":
    # execute only if run as a script
    main()

__main__ (opens new window)
トップレベルのコードが実行されるスコープの名前です。 モジュールが、標準入力から読み込まれたとき、 スクリプトとして実行されたとき、 あるいはインタラクティブプロンプトのとき、 __name__ には '__main__' が設定されます。

# 8. -m オプション

またコマンドラインから -m を指定して起動した場合、 sys.path を探索してから、パッケージ、またはモジュールをスクリプトとして実行します。

$ # timeit を使って 1/2 の実行時間を計測
$ python -m timeit -s "a,b=1,2" "a/b"
10000000 loops, best of 5: 38.6 nsec per loop
$ 
$ # sys.path などを表示してくれます。
$ python -m site
sys.path = [
    '/Users/ufo/current/directory',
    '/Users/ufo/.virtualenvs/pythonms/lib/python37.zip',
    '/Users/ufo/.virtualenvs/pythonms/lib/python3.7',
    '/Users/ufo/.virtualenvs/pythonms/lib/python3.7/lib-dynload',
    '/Users/ufo/.virtualenvs/pythonms/lib/python3.7/site-packages',
]
USER_BASE: '/Users/ufo/.local' (exists)
USER_SITE: '/Users/ufo/.local/lib/python3.7/site-packages' (doesn't exist)
ENABLE_USER_SITE: False
$ 
$ # pdb というデバッグのためのツールです。
$ # 自分はネットで落としたソースコードの
$ # 流れを追う時に使ったりします。
$ python3 -m pdb myscript.py

-m <module-name> (opens new window)
sys.path から指定されたモジュール名のモジュールを探し、 その内容を __main__ モジュールとして実行します。

5. インポートシステム - Python 言語リファレンス (opens new window)
PEP 338 はモジュールをスクリプトとして実行するときの仕様を定めています。

PEP 338 - Executing modules as scripts (opens new window)
This PEP defines semantics for executing any Python module as a script, either with the -m command line switch, or by invoking it via runpy.run_module(modulename).

動作を確認して見たところ、 __main__.py だけが実行されるわけではなく、 __main__.py が実行されたのちに __init__.py が実行されるという挙動をしていました。

あとなにをしているのか、よくわからないのですが -v を使うと、 起動時に様々なものを import している雰囲気がなんとなく伝わります。

$ python -v
import _frozen_importlib # frozen
import _imp # builtin
import '_thread' # <class '_frozen_importlib.BuiltinImporter'>
import '_warnings' # <class '_frozen_importlib.BuiltinImporter'>
import '_weakref' # <class '_frozen_importlib.BuiltinImporter'>
# installing zipimport hook
import 'zipimport' # <class '_frozen_importlib.BuiltinImporter'>
# installed zipimport hook
... 中略
import 'rlcompleter' # <_frozen_importlib_external.SourceFileLoader object at 0x1018a9588>
>>>

# 9. sys.moduels

sys.modules に束縛されます。そのためこれを消すと import も削除されます。 びっくりしました笑

# 対話モードにコピペして見てください
# ~/ $ cd ~/import/absolute/
# ~/import/absolute/ $ python
# >>>

import package
# 色々表示される。

import package
# 2回目は何もおこらない。
# import は2回目以降は名前束縛だけして実行はしません。

import dl
dl.exe()

import package
# 再び色々と表示される。

sys.modules (opens new window)
既に読み込まれているモジュールとモジュール名がマップされている辞書です。 これを使用してモジュールの強制再読み込みやその他のトリック操作が行えます。 ただし、この辞書の置き換えは想定された操作ではなく、 必要不可欠なアイテムを削除することで Python が異常終了してしまうかもしれません。

# 10. 相対 import と 絶対 import

これまでの import の書式は次のようなものでした。

import sys.pathからパッケージへのパス
import sys.pathからモジュールへのパス

パッケージ内のモジュール-モジュール モジュール-パッケージの参照の仕方として、 パスからの位置を指定する以外にも、相対関係の位置を指定する方法があります。

from .ファイルの位置からパッケージへのパス import オブジェクト
from .ファイルの位置からモジュールへのパス import オブジェクト

これを「相対 import」と呼びます。 いままで見てきたのは全て「絶対 import」になります。

相対 import が導入された背景などは PEP 328 に書かれていました。 翻訳しました。

# 10.1. 相対 import の2つの約束

# 10.1.1. パッケージの中でしか使えない。

相対 import は、パッケージの中でしか使えません。 これ、超重要です。 よく直接呼び出すスクリプトの中で $ python sample 相対 import を使って引っかかっている方を見かけます。

$ python
>>> import package  # <-- これは「パッケージ」として認識される。
$ python script  # <-- これは「スクリプト」として認識される。

# 10.1.2. from ... import ... としか書けない。

相対 import は from ... import ... だけしかかけず import ... とだけは書けません。 何故なら import を許してしまうと、式として使えなくなるからです。

# 10.2. やってみよう

# 10.2.1. 情報工学実験 第五

~/import/absolute/package 配下のスクリプトを、 相対 import に書き換えて、対話モードから import できるようにしてください。

~/import/absolute $ python
>>> import package

# 10.2.2. 情報工学実験 第五 解説

解答は ~/import/relative/package にあります。

パッケージとして利用 >>> import package する時は、 次のように規則性に則って書き換えていきます。

# 変更前
import package.sub_package.module_b

# 変更後
from .package.sub_package import module_b

# 10.2.3. 情報工学実験 第六

~/import/absolute/script 配下のスクリプトを、 相対 import に書き換えられるものは書き換えて、 コマンドラインから実行できるようにしてください。

~/import/absolute $ python script

# 10.2.4. 情報工学実験 第六 解説

スクリプトとして実行する python script 場合は、 全てを相対 import に書き換えることはできません。

なぜなら __main__.pymoduel_a.py がパッケージの中には無いからです。 繰り返しになりますが 相対 import は、パッケージの中でしか使えません。


# 11. 出力されるエラー

最後に improt 関係で発生するエラーを元に知識を再整理したいと思います。

# 11.1. ModuleNotFoundError の場合

そもそも Module が存在しないときに発生します。 「パッケージ」や「モジュール」をパスから見つけられなかった時に投げられる例外です。

ここで大事なのは、「パッケージ」や「モジュール」が、どこから検索されるのか、と言うことです。 import の書き方は、大きく分けて2種類あります。「絶対 import」と「相対 import」です。 絶対 import は sys.path からの位置を記述します。

# ◯ その1

email.mime.text を import しようとして import text としても MoudleNotFoundError になります。 これはパスを理解する必要があります。 詳細は こちら からどうぞ。

import text             # ModuleNotFoundError

import email.mime.text  # エラーにならない

# ◯ その 2

 相対 import   スクリプト  から実行しようとしています。 相対 import というのは、以下のように先頭にドット . のついた import 形式です。

from . import module
from .package import moduel

ここで大事なのは相対 import は  パッケージの中だけでしか使えない  ということです。

(1) スクリプトとして実行した時と (2) パッケージとして import した時の 動作の違いを理解する必要があります。 以下 teratail で同じものを見つけました。

詳細は こちら からどうぞ。

# 11.2. AttributeError の場合

モジュールはあったけど、その配下に属性が存在しなかった場合に発生します。 パッケージと、 パッケージとして import, 読み込まれたときに 最初に __init__.py が実行されていることを理解する必要があります。 詳細は こちら からどうぞ。

# 1)
import email
email.mime.text  # AttributeError

# 2)
import email.mime.text
email.mime.text  # エラーにならない

# 11.3. ImportError の場合

パッケージの外側から内側を覗こうとして、相対 import を使用すると発生します。

$ # script.py は「パッケージ」ではなく「スクリプト」である。
$ python script.py

__main__.py から相対 import をしようとして、 パッケージの外からパッケージの中を覗こうとしています。 ひとつ前の 0.3 は、__main__.py 以外のスクリプトから 相対 import をしようとする時にでるエラーです。 ややこしいので、統一してほしいです。

(1) スクリプトとして実行した時と (2) パッケージとして import した時の 動作の違いを理解する必要があります。 詳細は こちら からどうぞ。

# 11.4. ValueError の場合

パッケージの内側から外側を覗こうとして相対 import を使用すると発生します。 相対 import をしようとして、 パッケージの中から外を覗こうとしています。 スクリプトとして実行した時と、パッケージとして import された時の 動作の違いを理解する必要があります。 詳細は こちら からどうぞ。

# 12. おわりに

import は難しい。 3つの理由があります。 簡単に振り返りたいと思います。

まず、第一に  モジュール   パッケージ  で2種類あるからです。 パッケージはモジュールを集め、ディレクトリでひとまとめにされたものです。

また、第二に  パス  の概念を知る必要があります。 パスというのは、import する時にライブラリを検索する、ディレクトリへのパスのことです。 検索対象のディレクトリのパスは、1つだけではなく複数個あります。

そして、第三にコマンドラインから python sample.py と書いて  スクリプトとして実行した時  と、 他の Python スクリプトから import sample と書いて  ライブラリとして読み込んだ時  で、 import の挙動が変わってくるからです。

この文章では、これら3つの引っ掛かりどころ、それぞれ解消していきました。 import が呼ばれると「検索」して「実行」して「代入」しています。

以上になります。ここまでお付き合いいただき、誠にありがとうございました。