📘

Learning Python, 5th edition 読んでみたログ

2022/03/06に公開

はじめに

本はこれ
https://learning.oreilly.com/library/view/learning-python-5th/9781449355722/

また読むところが増えたら追記します

なんで読んだか

趣味で Python をさわるけどちょくちょくわからないことが出てくるので整理したい

読んでどうなりたいか

  • ふわっと Python に詳しくなりたい

1ヶ月後も覚えておきたいこと

  • fromimport の使い方
  • module import の流れ
  • search path のしくみ
  • ...

topics

modules と import

前提として、Python のプログラムは statements が書かれているテキストファイルから構成されていて、1つの top-level ファイルと0個以上の module からなる。module は top-level ファイルに使用される「ツール」で、プログラムを module に分割するのが Python の特徴のひとつ (modular programming)。module に分割すると、コードを再利用できる・名前空間を分けられるというメリットがある。

module は同時に名前空間 (namespace) でもあり、ある module A で定義された foo という name は module B では使えない。name とは変数または変数で、何もしないと top-level ファイルで他の module の関数とか変数を使えない。ここで、import が出てきて、import を通して他の module の name を使える ようになる。

# my_module.py

BAR = 999 # BAR という名前

def foo(): # foo という名前
  print('foo')

from と import の使い方

  • import: module 全体を使えるようにする
  • from: module の特定の name を使えるようにする
import Math # Math module を使うよ

print(Math.PI) # PI という name は Math. をつけて使う
from Math import PI # Math module の PI という name を使うよ

print(PI) # Math. はいらない

import したファイルと object の関係

import Math と書くと、PI定数を使うためには Math.PI のように object.attribute と同じ記法をする。これは偶然ではなくて、import では

  • module のファイルを特定する
  • module に書かれた name を attribute に持つ object をロードする
  • この object の名前を決める
    (i.e. Math module の中身を Math という名前の object にアサインする)

ということをしている。

ちなみに本文だとこういう表現だった↓

module file's global scope morphs into the module object's attribute namespace when it is imported (Chapter 22, p.691)

A file imports a module to gain access to the tools it defines, which are known as its attributes -- variable names attached to objects such as functions (Chapter 22, p.692)

なので、Math.PI と書くのは "Math module をロードしたオブジェクトの、attribute PI を通して name PI にアクセスする" という意味になる。

import の流れ

ファイルにインポート文があると、Python は

  1. ファイルを探して
  2. まだされてなかったらファイルをコンパイルして、最後に
  3. ファイルを実行する

1 では sys.path に書かれてるモジュール探索パス (search path) から同じ名前のファイルを探す (import foo とあったら foo.py を探す)。

2 ではバイトコードにファイルをコンパイルするけど、これは既にファイルの最新版がコンパイルされていたらしない。

3 ではバイトコードを走らせて、ファイルに書かれている名前 (def hoge とか) から module object の属性を生成 する。

pycache は何者?

↑で書いた、2の「バイトコードにコンパイルする」のところで、コンパイルしたバイトコードを保存するところが __pycache__/

Python のバージョンによって多少違いがあるけど、大まかな仕組みとしては .py ファイルのタイムスタンプバイトコードに埋め込まれたバージョン を比較して、すでに同じ Python のバージョンでファイルの最新版をコンパイルしたものが __pycache__/ にあれば、再度コンパイルしないようになってる。

search path / モジュール探索パス

こっちにもうちょっと詳しく書きました
https://zenn.dev/suyaa/articles/cbc691a4797e61#search-path-とは

  1. プログラムのホームディレクトリ
  2. 環境変数 PYTHONPATH
  3. standard library のディレクトリ
  4. .pth ファイル
  5. site-packages (i.e. pip install したときのインストール先)

からモジュールを探索する。

from, import のよい・だめな書き方

from some_module import * はよくない。インポートしている名前が分からないので、自分で変数・関数を定義したときや、別のライブラリをインポートしたときにうっかり名前が重複する可能性があるから。

# これはだめ
from some_module import *

逆に、from foo.bar import hoge はよい書き方。なぜかというと、このディレクトリ構造が変わったときに1つのファイルの変更箇所が1個ですむから。

# よい書き方

# BEFORE: foo/bar/hoge.py
from foo.bar import hoge

# AFTER: foo/bar/baz/hoge.py (もう一個 baz/ ディレクトリを作る)
from foo.bar.baz import hoge # 変更はここだけ

これを import foo としてファイル内で foo.bar.hoge として参照すると、ディレクトリ構造が変わると foo.bar.hoge を全部 foo.bar.baz.hoge に書き換えないといけないので大変。

# あんまりよくない書き方

# BEFORE: foo/bar/hoge.py
import foo

print(foo.bar.hoge.some_func)

# AFTER: foo/bar/baz/hoge.py (もう一個 baz/ ディレクトリを作る)
import foo

print(foo.bar.baz.hoge.some_func) # こういうのを全部書き換えないといけない

tuple って何?

tuple (タプル・テュープル) は配列だけど、list と違ってimmutableな配列。1個しか要素がないものは trailing comma がある

(foo,) # 要素が1個しかない tuple

Discussion