Si el objetivo es tener el mismo tipo de efecto en su código que #ifdef WINDOWS / #endif ... aquí hay una manera de hacerlo (estoy en una Mac por cierto).
Caso simple, sin encadenamiento
>>> 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)
Entonces, con esta implementación, obtienes la misma sintaxis que tienes en tu pregunta.
>>> @macos
... def zulu():
... print("world")
...
>>> @windows
... def zulu():
... print("hello")
...
>>> zulu()
world
>>>
Lo que está haciendo el código anterior, esencialmente, es asignar zulu a zulu si la plataforma coincide. Si la plataforma no coincide, devolverá zulu si se definió previamente. Si no se definió, devuelve una función de marcador de posición que genera una excepción.
Los decoradores son conceptualmente fáciles de entender si tienes en cuenta que
@mydecorator
def foo():
pass
es análogo a:
foo = mydecorator(foo)
Aquí hay una implementación que usa un 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")
Los decoradores parametrizados son análogos a foo = mydecorator(param)(foo)
.
He actualizado bastante la respuesta. En respuesta a los comentarios, amplié su alcance original para incluir la aplicación a los métodos de clase y para cubrir las funciones definidas en otros módulos. En esta última actualización, he podido reducir en gran medida la complejidad involucrada en determinar si una función ya ha sido definida.
[Una pequeña actualización aquí ... simplemente no pude dejar esto - ha sido un ejercicio divertido] He estado haciendo algunas pruebas más de esto, y descubrí que funciona en general en callables, no solo en funciones comunes; También puede decorar declaraciones de clase, ya sean invocables o no. Y es compatible con las funciones internas de las funciones, por lo que cosas como esta son posibles (aunque probablemente no sea un buen estilo, esto es solo un código de prueba):
>>> @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.")
Lo anterior demuestra el mecanismo básico de los decoradores, cómo acceder al alcance de la persona que llama y cómo simplificar múltiples decoradores que tienen un comportamiento similar al tener una función interna que contiene el algoritmo común definido.
Encadenamiento de apoyo
Para admitir el encadenamiento de estos decoradores que indican si una función se aplica a más de una plataforma, el decorador podría implementarse así:
>>> 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)
De esa manera, apoyas el encadenamiento:
>>> @macos
... @linux
... def foo():
... print("works!")
...
>>> foo()
works!
my_callback = windows(<actual function definition>)
, por lo que el nombremy_callback
se sobrescribirá, independientemente de lo que el decorador pueda hacer. La única forma en que la versión de Linux de la función podría terminar en esa variable es si sewindows()
devuelve, pero la función no tiene forma de conocer la versión de Linux. Creo que la forma más típica de lograr esto es tener las definiciones de funciones específicas del sistema operativo en archivos separados, y condicionalmenteimport
solo una de ellas.