Se o objetivo é ter o mesmo tipo de efeito em seu código que #ifdef WINDOWS / #endif tem ... aqui está uma maneira de fazer isso (estou em um mac btw).
Caso simples, sem encadeamento
>>> 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)
Portanto, com esta implementação, você obtém a mesma sintaxe que possui na sua pergunta.
>>> @macos
... def zulu():
... print("world")
...
>>> @windows
... def zulu():
... print("hello")
...
>>> zulu()
world
>>>
O que o código acima está fazendo, essencialmente, é atribuir zulu a zulu se a plataforma corresponder. Se a plataforma não corresponder, retornará o zulu se tiver sido definido anteriormente. Se não foi definido, ele retorna uma função de espaço reservado que gera uma exceção.
Os decoradores são conceitualmente fáceis de descobrir se você tiver em mente que
@mydecorator
def foo():
pass
é análogo a:
foo = mydecorator(foo)
Aqui está uma implementação usando um decorador parametrizado:
>>> 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")
Decoradores parametrizados são análogos a foo = mydecorator(param)(foo)
.
Eu atualizei a resposta um pouco. Em resposta aos comentários, ampliei seu escopo original para incluir aplicativos em métodos de classe e para cobrir funções definidas em outros módulos. Nesta última atualização, pude reduzir bastante a complexidade envolvida na determinação de se uma função já foi definida.
[Uma pequena atualização aqui ... Eu simplesmente não pude deixar isso para trás - foi um exercício divertido] Eu tenho testado mais isso e descobri que geralmente funciona em chamadas - não apenas em funções comuns; você também pode decorar declarações de classe, chamadas ou não. E ele suporta funções internas de funções, para que coisas como essa sejam possíveis (embora provavelmente não seja um bom estilo - este é apenas um código de teste):
>>> @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.")
O exemplo acima demonstra o mecanismo básico dos decoradores, como acessar o escopo do chamador e como simplificar vários decoradores que têm comportamento semelhante ao ter uma função interna contendo o algoritmo comum definido.
Suporte de encadeamento
Para suportar o encadeamento desses decoradores indicando se uma função se aplica a mais de uma plataforma, o decorador pode ser implementado da seguinte maneira:
>>> 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)
Dessa forma, você apoia o encadeamento:
>>> @macos
... @linux
... def foo():
... print("works!")
...
>>> foo()
works!
my_callback = windows(<actual function definition>)
- portanto, o nomemy_callback
será substituído, independentemente do que o decorador possa fazer. A única maneira pela qual a versão Linux da função poderia terminar nessa variável é se awindows()
retornasse - mas a função não tem como saber sobre a versão Linux. Eu acho que a maneira mais típica de conseguir isso é ter as definições de função específicas do SO em arquivos separados e, condicionalmente,import
apenas um deles.