Python3.7の新機能、__getattr__と__dir__関数を試してみた。
PEP 562 -- Module __getattr__ and __dir__ | Python.org
典型的な例としてRationaleに2つサンプルが記載されている。
- 廃止予定の警告の管理
- サブモジュールの遅延インポート
2つのうち、サブモジュールの遅延インポートについてサンプルを考えてみた。
データを読み込む際に、読み込み先がファイルとデータベースで切り替えられる仕組みについて実装した。
main.pyのloader_settingで、LoaderDefのどちらを代入するかで、読み込み先が変わる。
→ main.pyに書いてしまっているけど、これを他の設定と一緒に設定ファイルでまとめて定義するといいと思う
import importlib
__all__ = ['file_loader', 'db_loader']
def __getattr__(name):
if name in __all__:
return importlib.import_module("." + name, __name__)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
from abc import ABCMeta, abstractmethod
print('loader module')
class Loader(metaclass=ABCMeta):
print('Loader class')
@abstractmethod
def load(self):
pass
from getattr.loader import Loader
print('file_loader module')
class FileLoader(Loader):
print('FileLoader class')
def load(self):
print('FileLoader.load')
from getattr.loader import Loader
print('db_loader module')
class DBLoader(Loader):
print('DBLoader class')
def load(self):
print('DBLoader.load')
import enum
import getattr
class LoaderDef(enum.Enum):
FILE_LOADER = 'getattr.file_loader.FileLoader'
DB_LOADER = 'getattr.db_loader.DBLoader'
loader_setting = LoaderDef.FILE_LOADER
# loader_setting = LoaderDef.DB_LOADER
loader = eval(loader_setting.value)()
loader.load()
loader module
Loader class
file_loader module
FileLoader class
FileLoader.load
実行結果を見ると、db_loaderモジュールとDBLoaderクラスが呼ばれていないのがわかる。
なので、使わないオブジェクトの生成を防ぐことができている。
import enum
from getattr.file_loader import FileLoader
from getattr.db_loader import DBLoader
class LoaderDef(enum.Enum):
FILE_LOADER = 'FileLoader'
DB_LOADER = 'DBLoader'
loader_setting = LoaderDef.FILE_LOADER
# loader_setting = LoaderDef.DB_LOADER
loader = eval(loader_setting.value)()
loader.load()
loader module
Loader class
file_loader module
FileLoader class
db_loader module
DBLoader class
FileLoader.load
サブモジュールの遅延インポートを使わない場合は、db_loaderモジュールとDBLoaderクラスも呼ばれてしまう。
evalを使わずにもっとスマートに書く方法があるか考え中。
(main2.pyの場合、Enumのvalueは文字列じゃなくクラスでいいが、対比を示すために同様にevalを使っている)
補足
main.pyの最後に、
print(dir(getattr))
を追加して実行すると、
['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__getattr__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'file_loader', 'importlib', 'loader']
となり、file_loaderだけがgetattrのアトリビュートとなっていることがわかる。
以下に続く。
PEP 562 -- Module __getattr__ and __dir__を試してみた Part2 - Qiita