Python 入門講座 第11回: 名前って何? What's in a name?

プログラムを作成する際には、変数や関数、クラス、モジュールなど様々な名前が現れます。 これらの名前はその有効範囲(スコープ)の中では一意的に定まっている必要があります。

名前の有効範囲を把握しておかないと、プログラムが意図しない動作をする場合も考えられます。 (プログラムの違う場所で、同じ名前を違う意味/目的で使っていると、思わぬ動作をすることがあります。)

pythonプログラム中で導入された名前の有効範囲は一定のルールで定まっています。 ある時点での有効な名前の集合(辞書)を名前空間と呼んだりします。 (実際には、名前空間スコープは区別なく使われることがあるかもしれません。)

pythonではモジュール(プログラムファイル)、関数定義、クラス定義によって新たな名前空間が作られます。

名前のスコープの例(関数定義)

いま名前のスコープの例として、二つの数の和を返す関数を考えて見ます。

これを使って、次のpythonプログラムを実行したとします。

x=10; y=20 ; Y = 100
print(f"global {add2(3)=} , {x=}, {y=}")

add2(3)の値は 10 + 20 = 30 or 10 + 1 = 11 or 3 + 1 = 4 or 3 + 20 = 5のいずれになるでしょうか?

実際に試して見ましょう。

結果は、

add2(3) = 4 , x = 10, y = 20

となります。関数定義の中のxyは関数定義の外で定義されたxyとは無関係ということがわかります。 このことを、関数定義の中のyと関数定義の外で定義されたyは「異なるスコープを持つ」、 あるいは「別の名前空間に属している」といいます。

pythonでは、関数定義の中のyは関数add2localな名前空間に属しています。 一方, 関数定義の外で定義されたyはこのプログラムのglobalな名前空間に属しています。

さて、関数定義の中でx + yx + Yと書き間違えた時、なにが起きるかを見てみましょう。

このように、名前の有効範囲を正しく把握しておかないと、プログラムは動作するけれど、 意図した正しい結果を返さない場合もあるということです。

注意:PythonにはC/C++などのコンパイル言語と違って、変数名の宣言文はありません。 そのため、実行時に意図せず同じ変数名を異なる意味で使った時にも、エラーにならない場合があります。 pylintflake8などのツールを使うことで、プログラム実行前にこれらのエラーの可能性をチェックできます。

新しい名前が現れる場所

先ほどは、関数定義のブロックの中に現れる変数名を例に、名前空間について説明しました。

pythonで新しい名前が現れる(名前空間に新しい名前が追加される)場所は、次のようなものがあります。

これらの場所では、新しい名前にオブジェクトを結びつけています(束縛). 新しい名前が束縛されたときその名前はその時の名前空間(local 名前空間)に登録されます。

モジュール(~一つのpythonプログラムファイル)、関数定義、クラス定義は新しい名前空間を作成します。

global名前空間はモジュールの名前空間と組み込み名前空間(__builtins__の名前空間)を合わせた名前空間です。 組み込み名前空間には、pythonが標準的に提供する関数、定数、クラスなどの名前が登録されています。

__builtins__名前空間に登録されている名前は、

dir(__builtins__)

で確認することができます。

余談:

Pythonプログラムにはシステムがあらかじめ定義しているとして、def,ifなどプログラム言語としての文法要素である予約語 と pythonがあらかじめ用意している定数や関数などの組み込みオブジェクト名があります。 予約語は文法規則で決まっているため変更することができませんが、組み込みの変数や関数の名前は、他の名前(識別子, identifier)と同じように、別のオブジェクトに割り当てる(束縛する)こともできます。 しかし、組み込みオブジェクトの束縛を変更することは理解しにくいプログラムを作る元になりますので、避けましょう。 (True, False, Noneの三つの定数は予約後であると同時に、__builtins__ 名前空間にも登録されています。)

名前の解決

ある名前がプログラム中で使われる時、この名前に結び付けられている(束縛されている)オブジェクトを見つける必要があります。 Pythonではlocal()の名前空間から出発し、global()までの階層的な名前空間を1段階ずつ登りながら検索します。 globals()にもこの名前がみつからなければ、Errorが生成されます。

変数のスコープと名前空間

関数定義、クラス定義などのブロックはそれぞれの名前空間を持っています。 一つのファイルに含まれるプログラムテキストはグローバルな名前空間を定義しています。 プログラム中の名前はこの階層的な構造を持つ名前空間をlocalな名前空間からグローバル空間まで一段ずつ検索され、 束縛されているオブジェクトを見つけます。

pythonでは、関数定義のブロック、クラス定義のブロックの内部にクラス定義や関数定義を置けます。 ”関数プログラミング”のスタイルではこのような階層構造を反映して、名前空間 もglobal,locals

dir()関数

dir() 関数は、引数なしで実行された時、実行時のスコープに含まれる名前のリストを返します。

globals()関数とlocals() 関数

プログラムのある時点でのglobalなスコープを持つ名前の辞書(名前空間)をglobal()関数呼び出しで、 またlocalなスコープを持つ名前をlocal()関数呼び出しで確認できます。 例をみて見ましょう。

というように、add2(3)を実行している時のlocalスコープでは('x', 3), ('y', 1)などとなっています。 計算にはこれらのlocalスコープの値が使われ、globalスコープの値は影響を与えていません。

この関数定義で仮引数yの記述を忘れて、

def add2(x):
    info (f"local  scope:{[e for e in locals().items()]}")
    info (f"global scope:{[(k,v) for k,v in globals().items() if k in ('x','y')]}")
    return x+y

としてしまった場合を考えて見ます。

この場合には、関数add2(3)を実行する際のlocal名前空間には変数yがありません。pythonは名前空間をたどり、global空間にある名前yを見つけて、その値を使って関数の値を計算します。

この例のように、関数の値が関数定義の外の変数の影響を受けて変わってしまうと、関数の動作の確認/検証を難しくしてしまいます。 一般的にはこのような関数の定義は避けるべきものとされています。 しかしながら、問題によっては、関数外にシステムを特徴付ける数値(~定数)があって、それに基づいて処理を定義する ことを求められることがあります。このような場合にも、global宣言を使って関数定義外の変数を使っていることがを 明確する必要があります。

global 宣言と nonlocal 宣言

Pythonの名前の検索ルールから関数やクラスの中から一つ上のレベルの名前空間の変数に 束縛されたオブジェクトを入手することがで来ます。 しかし、この場合には内側の名前空間を持つブロックでは、このオブジェクトを変更することができません。 この場合、global宣言やnonlocal宣言を使うことで、内側の名前空間からこれらの変数に束縛されたオブジェクトを入手できます。 global,nolocal で宣言される変数は、宣言の文が実行される前にそれらの名前空間で定義(束縛)されている必要があります。

名前の付け方 (PEP 8から)

Pythonプログラムで使う名前では、ここまでで説明したように、名前の有効範囲に注意を払う必要があります。 名前付の際に一定のルールを採用することで、名前の衝突をさけ、プログラムの予期せぬ動作を避けることも 推奨されています。

pythonで使われる名前についてのガイドラインがPEP(Python Enhancement Proposals)に示されています。 完全にこれに従う必要はありませんが、見やすいPythonプログラムを作るための参考になるでしょう( PEP8の第2セクションもご覧ください)。

多くの方に使われるであろうモジュールを公開する際には、この命名規則に準拠することが推奨されます。 pylintなどのpythonプログラム診断ツールを使って、この命名規則に対するチェックを行えます。

実践されている命名方法の例

PEP8の「命名規則」の中で列挙されている名前付の方法の例を見て見ましょう。

関連する名前の集まりに、短い一意なプレフィックスを付けるやり方もあります。Python ではこのやり方を多く使っているわけではありませんが、完全を期すために紹介しておきます。

pythonプログラムの慣習として"_"(アンダースコア)で始まる(_single_leading_underscore) あるいは終わる(single_trailing_underscore_)名前には次のような意味が含まれています。

"_"(アンダースコア)で始まる, あるいは終わる名前

  1. 名前が一つの"_"で始まる名前(_single_leading_underscore): 内部的に使われる名前(non-public)を意味します。 moduleをfrom ... import *構文でimportする際には、これらの名前はimportされません。
  2. 名前が二つの"_"で始まる名前(__double_leading_underscore): クラスの属性名として使われた時、クラス内でのみ有効な属性名であることを意味します。(名前のマングリング機構によってクラス外からはこの名前ではアクセスすることはできません。)
  3. 名前が一つの"_"で終わる名前(single_trailing_underscore_): pythonのキーワードとの衝突を避けるために使われます。 例: tkinter.Toplevel(master, class_='ClassName')
  4. 名前が二つの"_"で始まり、二つの"_"で終わる名前(__double_leading_and_trailing_underscore__): pythonがシステムで持っている "マジック"オブジェクト または "マジック"属性です。 この種類の名前を新たに定義することは、将来問題を引き起こす可能性がありますので、避けなければなりません。

守るべき命名規約

PEP 8が述べている「守るべき命名規約」 を簡単にまとめておきます(一部を省略しています)。

こんな名前は嫌だ

単一の文字 'l' (小文字のエル)、'O' (大文字のオー)、'I'(大文字のアイ) を決して変数に使わないでください。

フォントによっては、これらの文字は数字の1や0と区別が付かない場合があります。'l'(小文字のエル) を使いたくなったら、'L' を代わりに使いましょう。

ASCII との互換性

標準ライブラリ で使われる識別子は、ASCII と互換性がなければなりません(PEP 3131)。

パッケージとモジュールの名前

モジュールの名前は、全て小文字の短い名前にすべきです。 読みやすくなるなら、アンダースコアをモジュール名に使っても構いません。

Pythonのパッケージ名は、全て小文字の短い名前を使うべきですが、アンダースコアを使うのは推奨されません。 (パッケージとは、複数のモジュールを階層的にまとめたものを、あたかも一つのモジュールのように取り扱う仕組みです。)

クラスの名前

クラスの名前には通常 CapWords 方式を使うべきです。

主に callable として使われる、ドキュメント化されたインターフェイスの場合は、クラスではなく関数向けの命名規約を使っても構いません。

Python にビルドインされている名前には別の規約があることに注意してください: ビルトインされている名前のほとんどは、単一の単語(または、二つの単語が混ざったもの) ですが、例外的に CapWords 方式が使われている名前や定数も存在しています。

例外の名前

例外はクラスであるべきです。よって、クラスの命名規約がここにも適用されます。 しかし、その例外が実際にエラーである場合には例外の名前の最後に "Error" をつけるべきです。

関数や変数の名前

関数の名前は小文字のみにすべきです。また、読みやすくするために、必要に応じて単語をアンダースコアで区切るべきです。

変数の名前についても、関数と同じ規約に従います。

mixedCase が既に使われている (例: threading.py) 場合にのみ、互換性を保つために mixedCase を許可します。

グローバル変数の名前

(ここで言う「グローバル変数」はモジュールレベルでグローバルという意味だと思いたいですが) ここで示す規約は、関数レベルのものと同じです。

from M import * 方式でimportされるように設計されているモジュールは、 グローバル変数をエクスポートするのを防ぐため all の仕組みを使うか、エクスポートしたくないグローバル変数の頭にアンダースコアをつける古い規約を使うべきです (こうすることで、これらのグローバル変数は「モジュールレベルで公開されていない」ことを開発者が示したいかもしれません)。

(モジュールのグローバル変数__all__にexportする名前(str)のリストを設定することで、from ... import *でimportされる名前を指定しておくことができます。)

定数 の名前

定数は通常モジュールレベルで定義します。全ての定数は大文字で書き、単語をアンダースコアで区切ります。例として MAX_OVERFLOWTOTAL があります。

関数やメソッドに渡す引数

メソッド名とインスタンス変数