Pythonはプログラム全体の__metaclass__を変更します
-
11-09-2019 - |
質問
編集: これは実稼働コードで行うには非常に悪いアイデアであることに注意してください。これは私にとってただ興味深いことでした。家ではこんなことしないでください!
Python でプログラム全体 (インタープリター) の __metaclass__ 変数を変更することは可能ですか?
この簡単な例は動作します。
class ChattyType(type):
def __init__(cls, name, bases, dct):
print "Class init", name
super(ChattyType, cls).__init__(name, bases, dct)
__metaclass__= ChattyType
class Data:
pass
data = Data() # prints "Class init Data"
print data
しかし、サブモジュールでも __metaclass__ を変更できるようにしたいと思っています。たとえば、(ファイル m1.py):
class A:
pass
a=A()
print a
ファイル main.py:
class ChattyType(type):
def __init__(cls, name, bases, dct):
print "Class init", name
super(ChattyType, cls).__init__(name, bases, dct)
__metaclass__= ChattyType
import m1 # and now print "Class init A"
class Data:
pass
data = Data() # print "Class init Data"
print data
グローバル __metaclass__ が Python 3.X で動作しなくなったことは理解していますが、それは私の懸念ではありません (概念実証であれば私のコードです)。では、Python-2.x でこれを実現する方法はあるのでしょうか?
解決
わかった;私の意見では、これはグロくて毛むくじゃらの黒魔術です。おそらく絶対に使用すべきではありませんが、特に運用コードでは使用しないでください。ただし、単なる好奇心のためには、ちょっと興味深いです。
で説明されているメカニズムを使用してカスタム インポーターを作成できます。 PEP302, 、さらに詳しくは Doug Hellmann の記事で説明されています。 PyMOTW:モジュールとインポート. 。これにより、検討したタスクを達成するためのツールが得られます。
興味があったので、このようなインポーターを実装しました。基本的に、クラス変数を使用して指定したモジュールに対して __chatty_for__
, 、カスタムタイプを __metaclass__
インポートされたモジュールの変数 __dict__
, 前に コードが評価されます。問題のコードが独自に定義している場合 __metaclass__
, 、インポーターによって事前に挿入されたものと置き換えられます。このインポーターがモジュールにどのような影響を与えるかを慎重に検討する前に、このインポーターをモジュールに適用することはお勧めできません。
私はインポーターをあまり書いたことがないので、これを書いている間に 1 つ以上の愚かなことをしてしまったかもしれません。私が実装で見逃した欠陥や例外的なケースに気づいた人がいたら、コメントを残してください。
ソースファイル1:
# foo.py
class Foo: pass
ソースファイル2:
# bar.py
class Bar: pass
ソースファイル3:
# baaz.py
class Baaz: pass
そしてメインイベント:
# chattyimport.py
import imp
import sys
import types
class ChattyType(type):
def __init__(cls, name, bases, dct):
print "Class init", name
super(ChattyType, cls).__init__(name, bases, dct)
class ChattyImporter(object):
__chatty_for__ = []
def __init__(self, path_entry):
pass
def find_module(self, fullname, path=None):
if fullname not in self.__chatty_for__:
return None
try:
if path is None:
self.find_results = imp.find_module(fullname)
else:
self.find_results = imp.find_module(fullname, path)
except ImportError:
return None
(f,fn,(suf,mode,typ)) = self.find_results
if typ == imp.PY_SOURCE:
return self
return None
def load_module(self, fullname):
#print '%s loading module %s' % (type(self).__name__, fullname)
(f,fn,(suf,mode,typ)) = self.find_results
data = f.read()
if fullname in sys.modules:
module = sys.modules[fullname]
else:
sys.modules[fullname] = module = types.ModuleType(fullname)
module.__metaclass__ = ChattyType
module.__file__ = fn
module.__name__ = fullname
codeobj = compile(data, fn, 'exec')
exec codeobj in module.__dict__
return module
class ChattyImportSomeModules(ChattyImporter):
__chatty_for__ = 'foo bar'.split()
sys.meta_path.append(ChattyImportSomeModules(''))
import foo # prints 'Class init Foo'
import bar # prints 'Class init Bar'
import baaz
他のヒント
「グローバル」 __metaclass__
Python 2 のこの機能は、モジュールごとにのみ動作するように設計されています (そうでない場合、その時点以降インポートしたすべてのライブラリおよびサードパーティ モジュールに独自のメタクラスを強制することによって、どのような大惨事が引き起こされるか考えてください -- 戦慄!) 。ある時点以降、インポートしているすべてのモジュールの動作を「密かに」変更することが非常に重要である場合、隠蔽と短剣の理由が何であれ、インポート フックを使用して非常に非常に汚いトリックを実行することができます (最悪の場合、まずソースを変更しながら一時的な場所にコピーする必要があります...) しかし、その労力は行為の巨大さに比例し、それが適切であると思われます;-)
いいえ。(これが特徴です!)