出所は、OSファイルでなくてもよく、HTTP、FTP、等のリソース、データベース項目、プログラム、等でもよく、モジュールコンテンツがダイナミックでもよい。
話題
About: Pythonプログラミング言語
この記事の目次
- 開始コンテキスト
- ターゲットコンテキスト
- オリエンテーション
- 本体
- 1: もしも、あるPythonモジュールファイルが、想定されたパスのものでなかったら、どうすればよいか?
- 2: もしも、あるPythonモジュールが、オペレーティングシステムファイル内にあるのでさえなかったら、どうするか?
- 3: 一つのわかりきったオプション
- 4: ある、よりよいかもしれないオプション
- 4-1: メカニズム
- 4-2: ソースローダークラスを作成する
- 4-3: ビルトインのファイルソースローダークラスがあるが . . .
開始コンテキスト
- 読者は、Pythonプログラミング言語の基本的知識を持っている。
ターゲットコンテキスト
- 読者は、任意の、ダイナミックでもよいモジュールを、任意の出所(オペレーティングシステムファイルでなくてもよく、HTTP、FTP、等リソース、データベース項目、プログラム、等)から、ダイナミックにインポートする方法を知る。
オリエンテーション
ここで紹介されるコードは、mypyアノテーションを含んでいます。もしも、それらに馴染みがなければ、ある以前の記事が充分な説明になるでしょう、または、それらは無視しても問題ありません。
本体
1: もしも、あるPythonモジュールファイルが、想定されたパスのものでなかったら、どうすればよいか?
Hypothesizer 7
通常、Pythonモジュールファイルは、ある想定されたオペレーティングシステムディレクトリ内に、想定されたファイル名で、存在します。
例えば、あるモジュール、'theBiasPlanet.coreUtilities.pipes.StringPipe'は、ファイル、'theBiasPlanet/coreUtilities/pipes/StringPipe.py'内にあり、その'theBiasPlanet'ディレクトリは、'PYTHONPATH'に登録されているあるディレクトリ内にあります。
もしも、そのモジュールが、'StringPipe.txt'などという名前のファイルにあったり、別のディレクトリ構造内にあったら、どうすればよいのか?
2: もしも、あるPythonモジュールが、オペレーティングシステムファイル内にあるのでさえなかったら、どうするか?
Hypothesizer 7
もしも、あるPythonモジュールが、オペレーティングシステムファイル内にあるのでさえなかったら、どうすればよいのか?
それは、どういうことか?
えーと、それは、あるHTTP、FTP、等リソース、あるデータベース項目、プログラム、等内にあるかもしれない。
そのモジュールは、特定のアルゴリズムによって生成された、ダイナミックなものでさえあるかもしれない。
3: 一つのわかりきったオプション
Hypothesizer 7
一つのわかりきったオプションは、当該モジュールコンテンツを、ある想定されたパスのファイル内に保存することだ。
言い換えれば、モジュールコンテンツはそのファイル内にキャッシュされるということだ。
何でいけない?
えーと、一部の人々は、反対する理由を持っているかもしれないし、他の人々は、そうでないかもしれない。
私は、個人的には、そのオプションを否定しないが、それを具体的にどう実装しようかと思い惑う: いつ、いかにして、そのキャッシングは呼び出されるべきか、いかにして、キャッシュされるべきモジュールが選択されるべきか(不要なモジュールを私はキャッシュしたくない)、いつ、いかにして、期限切れになったキャッシュは削除されるべきか、等。 . . . 不可能ではない、しかし、さほど苦痛なくというのでもない、と思われる。
4: ある、よりよいかもしれないオプション
Hypothesizer 7
ある、よりよいかもしれない方法は、'importlib'パッケージを使用することだ。
そのアドバンテージは、必要なモジュールのみが必要な時に取り出され、あとに何の残存物も残されないことで、私が上記に挙げた懸念が払拭される。
4-1: メカニズム
Hypothesizer 7
1つのソースローダーが使われ、それが、1つのモジュール名を受け取り、コンテンツバイト配列をリターンする。
以下が、それが動作する仕組みだ('HttpPythonSourceLoader'が、ソースローダークラス(私が作成した)、'theBiasPlanet_coreUtilities_pipes_StringPipe'は、モジュールオブジェクト(モジュール名は、'theBiasPlanet.coreUtilities.pipes.StringPipe'で、モジュールは、'http://localhost:8080/pythonSource/theBiasPlanet/coreUtilities/pipes/StringPipe.py'に存在する)。
@Python ソースコード
l_httpPythonSourceLoader: HttpPythonSourceLoader = HttpPythonSourceLoader ("http://localhost:8080/pythonSource/")l_pythonModuleName = "theBiasPlanet.coreUtilities.pipes.StringPipe"theBiasPlanet_coreUtilities_pipes_StringPipe = ModuleType (l_pythonModuleName)l_httpPythonSourceLoader.exec_module (theBiasPlanet_coreUtilities_pipes_StringPipe)sys.modules [l_pythonModuleName] = theBiasPlanet_coreUtilities_pipes_StringPipe
そのモジュールを'theBiasPlanet.coreUtilities.pipes.StringPipe'としてアクセスしたいですが、'theBiasPlanet_coreUtilities_pipes_StringPipe'としてではなく?
えーと、上記コード自体は、'theBiasPlanet'パッケージオブジェクト、等を自動的に作成しない、したがって、当該パッケージオブジェクト群をあなたが特に作成しなければならないだろう、もしも、あなたが本当にそれを必要するならば(私は必要としない)。
もしも、'from theBiasPlanet.coreUtilities.pipes.StringPipe import StringPipe'('StringPipe'クラスはそのモジュール内にある)のようにしたければ、以下を行なえばよい、上記コードの後に。
@Python ソースコード
StringPipe = theBiasPlanet_coreUtilities_pipes_StringPipe.StringPipe
4-2: ソースローダークラスを作成する
Hypothesizer 7
したがって、問題は、そのソースローダークラスをいかに作成するかということだ。
実のところ、以下が、そのソースローダークラスだ。
@Python ソースコード
from typing import Unionfrom typing import castfrom http.client import HTTPConnectionfrom http.client import HTTPResponsefrom http.client import HTTPSConnectionfrom importlib.abc import SourceLoaderimport urllib.parsefrom urllib.parse import ParseResultclass HttpPythonSourceLoader (SourceLoader):c_readingBlockSize: int = 1024def __init__ (a_this: "HttpPythonSourceLoader ", a_urlPrefix: str) -> None:a_this.i_urlPrefix: str = a_urlPrefixa_this.i_httpConnection: HTTPConnection = Nonel_parsedUrl: ParseResult = urllib.parse.urlparse (a_this.i_urlPrefix)if l_parsedUrl.scheme == "https":a_this.i_httpConnection = HTTPSConnection (l_parsedUrl.hostname, l_parsedUrl.port)else:a_this.i_httpConnection = HTTPConnection (l_parsedUrl.hostname, l_parsedUrl.port)def get_filename (a_this: "HttpPythonSourceLoader", a_pythonModuleName: str) -> str:return "{0:s}{1:s}.{2:s}".format (a_this.i_urlPrefix, a_pythonModuleName.replace (".", "/"), "py")def get_data (a_this: "HttpPythonSourceLoader", a_pythonModuleUrl: Union [bytes, str]) -> bytes:l_httpResponseBody: bytes = b""try:l_parsedUrl: ParseResult = urllib.parse.urlparse (cast (str, a_pythonModuleUrl))a_this.i_httpConnection.request ("GET", l_parsedUrl.path)l_httpResponse: HTTPResponse = a_this.i_httpConnection.getresponse ()if l_httpResponse.status == 200:l_httpResponseBodyBuffer: bytes = Nonewhile True:l_httpResponseBodyBuffer = l_httpResponse.read (HttpPythonSourceLoader.c_readingBlockSize)if len (l_httpResponseBodyBuffer) == 0:breakl_httpResponseBody = l_httpResponseBody + l_httpResponseBodyBufferelse:raise Exception ("The Python module source could not be accessed.")finally:a_this.i_httpConnection.close ()return l_httpResponseBody
それは、'importlib.abc.SourceLoader'抽象クラスの実装であり、2つのメソッド、'get_filename'および'get_data'を実装している。
'get_filename'メソッドは、モジュール名を受け取り、モジュールのURLをリターンするが、そのURLが'get_data'メソッドに渡され、後者メソッドがそのURLのコンテンツをリターンする。
データベース等にアクセスするソースローダーを書くのも、直線的に行なえる。
4-3: ビルトインのファイルソースローダークラスがあるが . . .
Hypothesizer 7
もしも、出所がオペレーティングシステムファイルであれば、ビルトインの'importlib.machinery.SourceFileLoader'クラスを使うことも可能ではある。
しかしながら、そのクラスは、モジュール名とファイルパスをコンストラクタで受け取る。
はあ?モジュール名とファイルパスが、そのクラスインスタンスに対して固定される?モジュール毎にインスタンスを作成しなければならないということか? . . . そういうことのようだ。
それは、親クラスの意図に反した奇妙な設計である: そのやり方では、'get_filename'メソッドが完全に無意味にされてしまっている。
モジュール毎にインスタンス化される必要のないものが欲しいのであれば、それをご自分で作成されるのは容易だろう。