【问题标题】:creating a signature of function parameters in Python在 Python 中创建函数参数的签名
【发布时间】:2018-01-08 21:17:04
【问题描述】:

我有一个函数需要很长时间,并且需要能够缓存它自己的结果,以便在使用相同参数再次调用它时。请参阅下面的示例,该示例似乎可以解决问题。我正在使用 Python 3.6

我的问题围绕着这条线:

param_sig = repr(locals())

1) 是否有更 Pythonic 的方式来获取传递给函数的参数的唯一签名?

2) 我可以依赖 Python 将函数参数插入到 locals() 映射中的顺序吗?同样,这似乎可行,但如果需要,我可以在不太优雅的签名创建者中明确地重新列出每个参数,例如:

param_sig = "{},{},{}".format(a,b,c)

示例代码:

import random
cached_answers = {}

def f(a=1, b=2, c=3):
    param_sig = repr(locals())
    if param_sig in cached_answers:
        ans = cached_answers[param_sig]
        print("Call: {} = CACHED {}".format(param_sig,ans))
        return ans
    else:
        # do heavy lifting then cache the result
        ans = random.random()
        print("Call: {} = {}".format(param_sig,ans))
        cached_answers[param_sig] = ans
        return ans

# various calls... some of which are repeated and should be cached
f()
f(b=9)
f(c=9, a=9)
f()               # should be cached
parms={'a':9}
f(**parms)
f(b=9)            # should be cached
f(a=9)            # should be cached

【问题讨论】:

标签: python python-3.x function parameters


【解决方案1】:

repr(locals()) 非常糟糕,因为它不适用于此功能。它会起作用,或者可以以其他方式起作用,我可以挖掘一些问题,而不是这样做的语义。

我会回到这个问题 - 首先,你的解决方案:

Python 有一个缓存功能,就像您在 stdlib 上的 functools 模块中所需要的一样 - 只需导入它并将其用作装饰器:

from functools import lru_cache

@lru_cache()
def f(a=1, b=2, c=3):
   # just put the heavy stuff inside your original "else" clause here

现在要了解为什么装饰器是一个更好的解决方案:缓存结果的逻辑根本不会与函数的逻辑混合,而且,任何用于缓存的方法都可以用于缓存中的任何函数您的程序 - 无需复制每个函数体内的缓存代码。

您将了解到,虽然 Python 自己的 lru_cache 与您的情况相匹配,但它并不是最佳匹配,也不是对所有情况都完美 - 无论如何,您最好安装 3rd 方包进行缓存或滚动您自己的缓存,但保持逻辑分离。

可以应用于系统中各种类似函数或方法的某种编程逻辑的想法也称为"aspect oriented programing",Python 装饰器语法是一种廉价使用它的好方法。

除了将逻辑与函数分离,并使用repr(locals()) 序列化参数之外,您的方法是正确的:为每组参数保留一个(模块)全局字典和结果是缓存函数的常用方法. lru_cache 只是以透明的方式发生。

【讨论】:

  • 谢谢。我的具体问题(获取参数签名)的答案在 lru_cache 代码中。如您所料,我的实际问题比缓存示例要复杂一些,但我没有考虑使用似乎是正确解决方案的装饰器。
【解决方案2】:

您可以通过使用 *args(它提供位置参数列表)或 **kwargs(“关键字”参数的字典)语法提供参数,从而以确定的顺序访问参数。考虑:

>>> def fn(*args):
...     for i in args: print(i)
... 
>>> fn('a', 'b', 'c')
a
b
c

>>> def fn2(**kwargs):
...     for k in kwargs.keys(): print("{}: {}".format(k, kwargs[k]))
... 
>>> fn2(a='aval', b='bval', c='cval')
a: aval
c: cval
b: bval

要在缓存中使用这些,您需要将 args 转换为 dict 键。 我建议不要将其格式化为字符串,而是将元组用作缓存键。元组是不可变的,因此它们可以被散列(对字典的要求)。示例:

>>> cache=dict()
>>> def fn3(**kwargs):
...     key=(kwargs['a'], kwargs['b'], kwargs['c'])
...     val = ':'.join( (kwargs['a'], kwargs['b'], kwargs['c']) )
...     print(str(key))
...     print(val)
...     cache[key] = val
...     return
... 
>>> fn3(a='1', b='2', c='3')
('1', '2', '3')
1:2:3
>>> print(str(cache))
{('1', '2', '3'): '1:2:3'}

【讨论】:

  • 谢谢,杰夫。看起来这假设所有参数都没有默认值,并更改了函数代码,因为 kwargs 参数不能作为函数内部的局部变量使用
  • 我喜欢使用元组作为键的想法。
猜你喜欢
  • 2022-08-04
  • 1970-01-01
  • 2020-08-10
  • 2010-11-27
  • 1970-01-01
  • 2011-12-03
  • 2011-06-02
  • 2016-01-01
  • 2021-10-07
相关资源
最近更新 更多