首页 > 解决方案 > 如何用装饰器绕过python函数定义?

问题描述

我想知道是否可以根据全局设置(例如操作系统)来控制 Python 函数定义。例子:

@linux
def my_callback(*args, **kwargs):
    print("Doing something @ Linux")
    return

@windows
def my_callback(*args, **kwargs):
    print("Doing something @ Windows")
    return

然后,如果有人在使用 Linux,将使用第一个定义,my_callback而第二个定义将被忽略。

它不是关于确定操作系统,而是关于函数定义/装饰器。

标签: pythonpython-3.xfunctionpython-decorators

解决方案


如果目标是在您的代码中产生与#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!

下面的评论在目前的状态下并不真正适用于这个解决方案。它们是在寻找解决方案的第一次迭代中制作的,不再适用。例如语句,“请注意,这仅在 macOS 和 Windows 与 zulu 定义在同一模块中时才有效。” (upvoted 4次) 适用于最早的版本,但已在当前版本中解决;以下大多数陈述都是这种情况。奇怪的是,验证当前解决方案的评论已被删除。


推荐阅读