【问题标题】:Python decorator taking additional argument anti-patternPython 装饰器采用额外的参数反模式
【发布时间】:2021-04-11 20:54:34
【问题描述】:

通过包装器向函数添加参数是 python 反模式吗?我想添加一个包装器,将许多函数的输出保存到一个位置,所以包装器似乎很有意义。但是,Pycharm 无法自动完成修饰函数的参数 (https://intellij-support.jetbrains.com/hc/en-us/community/posts/360002754060-Autocomplete-with-arguments-for-decorated-functions)。

一些与使用包装器更改函数签名相关的讨论似乎表明这是一种不好的做法 (https://youtrack.jetbrains.com/issue/PY-33688#focus=Comments-27-3268273.0-0)。

一些装饰器可能会更改函数的签名,以便正确处理此类情况 PyCharm 必须读取由于性能原因而无法完成的装饰器主体。

因此,执行以下操作会是一种反模式:

from functools import wraps
from typing import Callable

my_dict = {"very": {"deeply": {"nested": {"filepath": "hidden_filepath"}}}}


def decorator(function: Callable):
    @wraps(function)
    def wrapper(extra_arg: str, function_arg: str) -> str:
        file_path: str = my_dict["very"]["deeply"]["nested"][extra_arg]
        print(f"saving to: {file_path}")
        result: str = function(function_arg)
        print(f"result: {result}")
        return result

    wrapper.__doc__ += "/n:param extra_arg: an extra argument"
    return wrapper


@decorator
def my_function(an_arg: str) -> str:
    """
    my docstring
    :param an_arg:
    :return:
    """
    print(f"my_function arg: {an_arg}")
    return an_arg * 2


my_function("filepath", "cool_str")

我也不喜欢在函数中附加文档字符串,但在这里找到了解决方案:Signature-changing decorator: properly documenting additional argument。只更改装饰函数的文档字符串是否更有意义?

编辑: 我能想到的唯一其他合理的解决方案是创建一个将另一个函数作为参数的函数,这是包装器应该解决的问题,例如。

def decorator(extra_arg:str, function: Callable, **kwargs)-> str:
    file_path: str = my_dict["very"]["deeply"]["nested"][extra_arg]
    print(f"saving to: {file_path}")
    result: str = function(**kwargs)
    print(f"result: {result}")
    return result

def my_function(an_arg: str) -> str:
    """
    my docstring
    :param an_arg:
    :return:
    """
    print(f"my_function arg: {an_arg}")
    return an_arg * 2

decorator("filepath", my_function, an_arg="cool_str")

【问题讨论】:

  • 不确定“反模式”部分,但如果我看到这样的代码,我可能会重构它。幕后花絮太多了。
  • @rdas,看我的编辑,那是你要重构的吗?它似乎不是一个优越的解决方案,你没有从你的 IDE 中得到任何参数提示,重构不是 IDE 辅助的,等等。
  • 取决于您的实际程序,但我不希望看到每个函数调用都通过另一个函数调用。可能有更简单的方法来做你想做的事情,但这需要更多关于细节的细节。
  • @rdas,谢谢,同意。我基本上是在尝试做上述事情。将任何随机函数的结果保存到其路径存储在深度嵌套字典中的文件中。我还想在函数调用周围有一些打印语句(我目前绑定到 print() 作为我的记录器)

标签: python decorator python-decorators anti-patterns functools


【解决方案1】:

考虑可维护性。

维护您的代码的其他人会看到 my_function 只有一个 arg。 PyCharm 和mypy 会尖叫着用多个参数调用my_function 是错误的。然后其他人去“修复”所有“错误”。

你的程序中断了。

在发现您的装饰器更改了函数的签名之前进行了数小时的故障排除。

哎呀,不需要是其他人...将您的代码保留一两个月,当您返回时,您可能会忘记您的装饰器破坏了您的功能...

所以是的,这是一种不好的做法,一种反模式,一种代码味道,一种。

【讨论】:

  • 谢谢,但似乎使用添加参数的包装器可以解决很多常见问题,例如将结果记录到文件等。我缺少更好的解决方案吗?
  • ???老实说,我不明白为什么你需要一个额外的 arg 来将结果记录到文件中......
  • 您需要该文件才能登录。
  • 为什么?您可以在函数内执行logger = logging.getLogger("somename")。让程序的初始化部分进行日志系统配置。一个同名的 Logger 对象是一个单例;无论从哪里获取,它们都将共享相同的处理程序等。
  • 这只是一个例子,并不是讨论的核心。但是假设你想记录到不同的任意文件,并且你想记录任何函数的执行时间。所以你需要总是调用包裹在定时器块中的函数,并且需要不同目标文件的灵活性。
猜你喜欢
  • 1970-01-01
  • 2012-04-27
  • 1970-01-01
  • 2018-11-01
  • 2016-09-09
  • 2021-03-20
  • 2014-07-21
相关资源
最近更新 更多