#ifdef WINDOWS / #endifと同じような効果をコードに持たせることが目標の場合は、次の方法で行います(Macを使用しています)。
単純なケース、連鎖なし
>>> def _ifdef_decorator_impl(plat, func, frame):
... if platform.system() == plat:
... return func
... elif func.__name__ in frame.f_locals:
... return frame.f_locals[func.__name__]
... else:
... def _not_implemented(*args, **kwargs):
... raise NotImplementedError(
... f"Function {func.__name__} is not defined "
... f"for platform {platform.system()}.")
... return _not_implemented
...
...
>>> def windows(func):
... return _ifdef_decorator_impl('Windows', func, sys._getframe().f_back)
...
>>> def macos(func):
... return _ifdef_decorator_impl('Darwin', func, sys._getframe().f_back)
したがって、この実装では、質問と同じ構文が得られます。
>>> @macos
... def zulu():
... print("world")
...
>>> @windows
... def zulu():
... print("hello")
...
>>> zulu()
world
>>>
上記のコードが実行しているのは、基本的に、プラットフォームが一致する場合にzuluをzuluに割り当てることです。プラットフォームが一致しない場合、以前に定義されていればzuluを返します。定義されていない場合は、例外を発生させるプレースホルダー関数を返します。
あなたがそれを覚えていれば、デコレータは概念的に理解しやすいです
@mydecorator
def foo():
pass
に似ています:
foo = mydecorator(foo)
パラメータ化されたデコレータを使用した実装は次のとおりです。
>>> def ifdef(plat):
... frame = sys._getframe().f_back
... def _ifdef(func):
... return _ifdef_decorator_impl(plat, func, frame)
... return _ifdef
...
>>> @ifdef('Darwin')
... def ice9():
... print("nonsense")
パラメータ化されたデコレータはに似ていfoo = mydecorator(param)(foo)
ます。
私は答えをかなり更新しました。コメントに応じて、元のスコープを拡張して、アプリケーションをクラスメソッドに含め、他のモジュールで定義された関数をカバーしました。この最後の更新では、関数が既に定義されているかどうかを判断する際の複雑さを大幅に軽減することができました。
[ここで少し更新...これを書き留めることができませんでした-それは楽しい練習でした]私はこれのいくつかのテストを行っており、通常の関数だけでなく、呼び出し可能オブジェクトで一般的に機能することを発見しました。呼び出し可能かどうかにかかわらず、クラス宣言を装飾することもできます。そしてそれは関数の内部関数をサポートしているので、このようなことが可能です(おそらく良いスタイルではありません-これは単なるテストコードです):
>>> @macos
... class CallableClass:
...
... @macos
... def __call__(self):
... print("CallableClass.__call__() invoked.")
...
... @macos
... def func_with_inner(self):
... print("Defining inner function.")
...
... @macos
... def inner():
... print("Inner function defined for Darwin called.")
...
... @windows
... def inner():
... print("Inner function for Windows called.")
...
... inner()
...
... @macos
... class InnerClass:
...
... @macos
... def inner_class_function(self):
... print("Called inner_class_function() Mac.")
...
... @windows
... def inner_class_function(self):
... print("Called inner_class_function() for windows.")
上記は、デコレーターの基本的なメカニズム、呼び出し元のスコープにアクセスする方法、および共通のアルゴリズムを含む内部関数を定義して、同様の動作を持つ複数のデコレーターを簡略化する方法を示しています。
連鎖サポート
関数が複数のプラットフォームに適用されるかどうかを示すこれらのデコレーターのチェーンをサポートするには、デコレーターを次のように実装できます。
>>> class IfDefDecoratorPlaceholder:
... def __init__(self, func):
... self.__name__ = func.__name__
... self._func = func
...
... def __call__(self, *args, **kwargs):
... raise NotImplementedError(
... f"Function {self._func.__name__} is not defined for "
... f"platform {platform.system()}.")
...
>>> def _ifdef_decorator_impl(plat, func, frame):
... if platform.system() == plat:
... if type(func) == IfDefDecoratorPlaceholder:
... func = func._func
... frame.f_locals[func.__name__] = func
... return func
... elif func.__name__ in frame.f_locals:
... return frame.f_locals[func.__name__]
... elif type(func) == IfDefDecoratorPlaceholder:
... return func
... else:
... return IfDefDecoratorPlaceholder(func)
...
>>> def linux(func):
... return _ifdef_decorator_impl('Linux', func, sys._getframe().f_back)
このようにして、連鎖をサポートします。
>>> @macos
... @linux
... def foo():
... print("works!")
...
>>> foo()
works!
my_callback = windows(<actual function definition>)
- と同等です。そのため、デコレーターが何をするかに関係なく、名前my_callback
は上書きされます。関数のLinuxバージョンがその変数に入る唯一の方法は、windows()
それが返された場合です-しかし、関数はLinuxバージョンを知る方法がありません。これを達成するためのより一般的な方法は、OS固有の関数定義を別々のファイルに入れ、条件付きimport
でそれらの1つだけにすることだと思います。