Python globals()を使用した動的クラスの最適なアプローチ
-
03-07-2019 - |
質問
ユーザーの入力に応じて、モジュールの変数セットを返すWebアプリケーションで作業しています。各モジュールは、単一のパラメーターを受け入れ、出力を含む '.html'プロパティを持つコンストラクターを持つPythonクラスです。
グローバル名前空間からクラスを動的にプルすることは機能します:
result = globals()[classname](param).html
そして、それは確かに以下よりも簡潔です:
if classname == 'Foo':
result = Foo(param).html
elif classname == 'Bar':
...
これをスタイリスト的に書く最良の方法は何ですか?グローバル名前空間を使用しないリスクや理由はありますか?
解決
このアプローチの欠点は、ユーザーが望む以上の機能をユーザーに提供する可能性があることです。名前を指定するだけで、そのネームスペース内の単一パラメータ関数 any を呼び出すことができます。いくつかのチェック(例:isinstance(SomeBaseClass、theClass))でこれを防ぐことができますが、おそらくこのアプローチを避ける方が良いでしょう。別の欠点は、クラスの配置が制約されることです。それらをモジュールにグループ化するには、ルックアップコードが機能しなくなります。
いくつかの代替オプションがあります:
-
明示的なマッピングを作成します:
class_lookup = {'Class1' : Class1, ... } ... result = class_lookup[className](param).html
これには、すべてのクラスを再リストする必要があるという欠点があります。
-
クラスを囲むスコープに入れ子にします。例えば。独自のモジュール内、または外部クラス内で定義します:
class Namespace(object): class Class1(object): ... class Class2(object): ... ... result = getattr(Namespace, className)(param).html
ここでいくつかの追加のクラス変数(__bases __、__ getattribute__など)を誤って公開している-おそらく悪用できませんが、完璧ではありません。
-
サブクラスツリーから検索辞書を作成します。すべてのクラスが単一のベースクラスから継承するようにします。すべてのクラスが作成されたら、すべてのベースクラスを調べ、それらから辞書を作成します。これには、クラスをどこでも(たとえば、個別のモジュールで)定義できるという利点があり、すべての作成後にレジストリを作成する限り、それらを見つけることができます。
def register_subclasses(base): d={} for cls in base.__subclasses__(): d[cls.__name__] = cls d.update(register_subclasses(cls)) return d class_lookup = register_subclasses(MyBaseClass)
上記のより高度なバリエーションは、自己登録クラスを使用することです-作成されたクラスを自動的に辞書に登録するよりもメタクラスを作成します。これはおそらくこの場合にはやり過ぎです-一部の「ユーザープラグイン」で便利です。ただし、シナリオ。
他のヒント
まず第一に、あなたは車輪を少し再発明しているように思えます...ほとんどのPython Webフレームワーク(CherryPy / TurboGearsは私が知っていることです)にはすでに、 URL、またはユーザー入力。
あなたのやり方には間違っているということはまったくありませんが、私の経験では、ある種の「抽象化の欠如」を示す傾向があります。あなたのプログラムで。基本的に、Pythonインタープリターを使用して、自分で保存するのではなく、必要なオブジェクトのリストを保存します。
そのため、最初のステップとして、呼び出したいすべてのクラスの辞書を作成したい場合があります:
dispatch = {'Foo': Foo, 'Bar': Bar, 'Bizbaz': Bizbaz}
最初は、これで大きな違いはありません。ただし、Webアプリが成長するにつれて、いくつかの利点が見つかる場合があります。(a)名前空間の衝突に遭遇しない、(b) globals()
を使用すると、攻撃者ができるセキュリティ問題が発生する可能性があります。本質的に、プログラムに任意の classname
を注入する方法を見つけることができる場合、プログラム内の任意のグローバルシンボルにアクセスします。(c) classname
を何かにしたい場合実際の正確なクラス名以外に、独自の辞書を使用するとより柔軟になります。(d) dispatch
辞書を、データベースアクセスなどを行う、より柔軟なユーザー定義クラスに置き換えることができます。あなたは必要を見つけます。
セキュリティの問題は、Webアプリにとって特に顕著です。 variables
がWebフォームから入力される場所で globals()[variable]
を実行することは、単にトラブルを要求する。
クラス名とクラス間のマップを構築する別の方法:
クラスを定義するときに、ルックアップテーブルに配置するクラスに属性を追加します。例:
class Foo:
lookup = True
def __init__(self, params):
# and so on
これが完了したら、ルックアップマップの構築は次のとおりです。
class_lookup = zip([(c, globals()[c]) for c in dir() if hasattr(globals()[c], "lookup")])