Якщо мета полягає у тому, щоб у вашому коді був такий самий ефект, що і #ifdef WINDOWS / #endif .. ось спосіб це зробити (я на mac btw).
Простий кейс, без прикування
>>> 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
>>>
Те, що робиться вище кодом, по суті, призначає зулу зулу, якщо платформа збігається. Якщо платформа не збігається, вона поверне зулу, якщо вона була визначена раніше. Якщо його не було визначено, він повертає функцію заповнення місця, яка викликає виняток.
Декораторів концептуально легко зрозуміти, якщо ви це пам’ятаєте
@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. Я думаю, що більш типовим способом досягнення цього є визначення специфічних для ОС функцій в окремих файлах і, умовно,import
лише в одному з них.