初めてのPython(21章)

モジュールのカプセル化

Pythonではカプセル化は出来ない。
アンダースコア(_)を使うことで、データを隠蔽することは出来るPEP8

  • 1個の場合、from ~ import * でインポートできないようになる
  • 2個の場合、モジュール(クラス無し)の時は1個の時と同じ動きだが、クラス属性だと属性名の前に_クラス名が付いた名称に変更される
  • ただ、カプセル化で来ている訳ではないので、属性名を指定するとアクセスできてしまう。

__all__属性に該当する属性をリストアップしなければ、アンダースコア1個と同じ動きができる。
けど、__all__属性は__init__.pyに記述するからパッケージインポートの時にしか使えない。

(test_module.py)
#!/usr/bin/env python

x = 10
_y = 20
__z = 30

(test_class.py)
#!/usr/bin/env python

class sample1:
  x = 10
  _y = 20
  __z = 30

class _sample2:
  x = 100
  _y = 200
  __z = 300

[kobakoba0723@fedora13-intel64 pkg_import]$ python
Python 2.6.4 (r264:75706, Apr  1 2010, 02:55:51) 
[GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
>>> from test_module import *
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'x']
>>> import test_module
>>> dir(test_module)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__z', '_y', 'x']
>>> 
>>> from test_class import *
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sample1', 'test_module', 'x']
>>> import test_class
>>> dir(test_class)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '_sample2', 'sample1']
>>> 
>>> sample1 = sample1()
>>> dir(sample1)
['__doc__', '__module__', '_sample1__z', '_y', 'x']
>>> sample2 = test_class._sample2()
>>> dir(sample2)
['__doc__', '__module__', '_sample2__z', '_y', 'x']
>>> 
>>> test_module.x, test_module._y, test_module.__z
(10, 20, 30)
>>> 
>>> sample1.x, sample1._y, sample1._sample1__z
(10, 20, 30)
>>> 
>>> test_module.__z = 100
>>> test_module.x, test_module._y, test_module.__z
(10, 20, 100)
>>> 
>>> sample1._sample1__z = 100
>>> sample1.x, sample1._y, sample1._sample1__z
(10, 20, 100)
>>> 

相対インポートと絶対インポート

モジュールの探索先をカレントパスから行うのか、モジュールサーチパスから行うかの違い

種別 探索先
相対インポート カレントパス→モジュールサーチパス
絶対インポート モジュールサーチパス

相対インポートだと、標準モジュールと同名のモジュールが”たまたま”カレントパスにあった場合に、標準モジュールが使われない。
それを防ぐために、将来的には通常のインポートが絶対インポートになる。
(from __future__ import absolute_import を使うことで有効化可能)

相対インポートは from ~ import ステートメントでしか使えないインポート方法で、"."をつけることで相対インポートを行う。
"."を1つ追加する毎に1階層親のディレクトリから探索が始まる

以下のようなディレクトリ構造の場合、moduleXから相対インポートをするには、

relative_import
-- __init__.py
-- main.py
-- package
|-- __init__.py |-- moduleA.py (属性Aを定義) |-- bar.py |-- subpackage1 | |-- __init__.py | |-- moduleX.py (属性Xを定義) | |-- moduleY.py (属性Yを定義) |-- subpackage2 |-- __init__.py |-- moduleZ.py (属性Zを定義)
(兄弟モジュールをインポート)
from . import moduleY
from ..subpackage1 import moduleY

(兄弟モジュールの属性をインポート)
from .moduleY import Y
from ..subpackage1 import Y

(兄弟パッケージのモジュールをインポート)
from ..subpackage2 import moduleZ

(兄弟パッケージのモジュールの属性をインポート)
from ..subpackage2.moduleZ import Z

(親パッケージのモジュールをインポート)
from .. import moduleA

(親パッケージのモジュールの属性をインポート)
from ..moduleA import A

ただし、以下のようなコードを書くとエラー(*1)が出てしまう(※main.pyに記述して、python main.py とやった場合)

from ...package import bar

(*1) ValueError: Attempted relative import beyond toplevel package

relative_import と同一階層に main.py を配置して同様に実行した場合はエラーが出ない... なんでだろう。
package ディレクトリにも __init__.py を配置してるからパッケージとして扱われると思ってたんだけど。。。

メタプログラム

ビルドイン属性を利用して、他のモジュールを管理/操作するプログラムのこと
メタプログラムは、イントロスペクションとも呼ばれる。

モジュールのある属性にアクセスする場合、以下のような方法がとれる

  • モジュール名.属性名
  • モジュール名.__dict__['属性名']
  • sys.modules['モジュール名'].属性名
  • getattr('モジュール名', '属性名')

その他

拡張機能の有効化は以下で行う。

  • from __future__ import 機能名

トップレベルファイルとして使われているのか、インポートされて使われているかの切り分けに”__name__"が使える

  • トップレベルファイルの場合は __main__ が設定される
  • インポートの場合は モジュール名 が設定される

モジュールに別名をつける機能があり、as を使う

  • import module名 as 別名
  • from module import attribute as 別名

from ステートメントでインポートした属性は reload() の影響を受けない。
reload を使用する場合は、import ステートメントを使う。