Python如何将不同的**kwargs传递给多个函数?

47

通过Python文档和Stack Overflow,我理解了如何在def函数中使用**kwargs。然而,我有一个情况需要为两个子函数使用两组**kwargs。有人能展示一下如何正确分离这些**kwargs吗?

这是我的目标:绘制一组点和插值平滑曲线,
这是我的简单样例代码:

def smoothy(x,y, kind='cubic', order = 3, **kwargs_for_scatter, **kwargs_for_plot):
    yn_cor = interp1d(x, y, kind=kind, assume_sorted = False)
    xn = np.linspace(np.min(x), np.max(x), len(x) * order)
    plt.scatter(x,y, **kwargs_for_scatter)
    plt.plot(xn, yn_cor(xn), **kwargs_for_plot);
    return

感谢您的帮助。


7
只需像普通的字典一样传递 scatterplot,它们似乎是必需的参数...然后在函数中使用 **scatter**plot...你不需要对它们进行其他操作。 - Jon Clements
4个回答

40

目前没有这种机制。有一个提案,PEP-448,Python 3.5及以后版本中推广了参数解包。Python 3.4及之前的版本不支持此功能。一般情况下,你能做到的最好的方式是:

def smoothy(x,y, kind='cubic', order = 3, kwargs_for_scatter={}, kwargs_for_plot={}):
    yn_cor = interp1d(x, y, kind=kind, assume_sorted = False)
    xn = np.linspace(np.min(x), np.max(x), len(x) * order)
    plt.scatter(x,y, **kwargs_for_scatter)
    plt.plot(xn, yn_cor(xn), **kwargs_for_plot);
    return

然后将这些选项作为字典传递,而不是 kwargs,传递给 smoothy

smoothy(x, y, 'cubic', 3, {...}, {...})

由于变量名可能被暴露给调用者,因此您可能希望将它们重新命名为更短的名称(例如scatter_optionsplot_options)。

更新:Python 3.5和3.6已成为主流版本,它们确实支持基于PEP-448的扩展解包语法。

>>> d = {'name': 'joe'}
>>> e = {'age': 20}
>>> { **d, **e }
{'name': 'joe', 'age': 20}

然而,在这种旨在为多个目的地提供kwargs的情况下,这并没有什么帮助。即使smoothy()函数使用了一个统一的kwargs抓包,你仍需要确定它们中的哪些是针对哪些子函数的。最好也很混乱。多个dict参数,其中一个旨在传递给每个kwarg-taking子函数,仍然是最佳方法。


正如我在这里所建议的那样,现在没有必要再将“kwargs_for_”保留在参数中了...(无论如何,在我看来是这样) - Jon Clements
@JonClements 看起来我们遇到了竞态条件/并发编辑,但得出了相同的结论! - Jonathan Eunice
1
没问题...非常好的答案(虽然我有点偏见 咳咳)...我只是不确定是否如此简单或是否需要更复杂的东西。感谢你明确解释和提供选项,我给你一个赞 :) - Jon Clements

23

另一种不同的方法

我意识到我有点晚来参加这个聚会。然而,当我处理由几个其他类组成的类时,我遇到了类似的问题。我想避免为每个子类(或-函数)传递字典,复制所有组件类的参数并且在以后可能不得不更新它们的风险非常反DRY。

我的解决方案肯定不是最短的,也不是很好看,但我认为它有一定的优雅。我修改了下面的smoothy函数:

import inspect

def smoothy(x,y, kind='cubic', order = 3, **kwargs):
    yn_cor = interp1d(x, y, kind=kind, assume_sorted = False)
    xn = np.linspace(np.min(x), np.max(x), len(x) * order)
    
    scatter_args = list(inspect.signature(plt.scatter).parameters)
    scatter_dict = {k: kwargs.pop(k) for k in dict(kwargs) if k in scatter_args}
    plt.scatter(x,y, **scatter_dict)
    
    plot_args = list(inspect.signature(plt.plot).parameters)
    plot_dict = {k: kwargs.pop(k) for k in dict(kwargs) if k in plot_args}
    plt.plot(xn, yn_cor(xn), **plot_dict);
    return

解释

首先,使用 inspect.signature() 创建一个第一个函数(scatter)接受的参数列表 (scatter_args)。然后从 kwargs 构造一个新字典 (scatter_dict),只提取与我们的参数列表中相同的项目。在这里使用 dict(kwargs) 确保我们循环遍历 kwargs 的副本,以便我们可以修改原始内容而不会出现错误。然后将这个新字典传递给函数 (scatter),并重复以下步骤处理下一个函数。

一个潜在的问题是,由于它现在是一个单独的字典,kwargs 中的参数名称可能不重复。因此,对于您无法控制参数名称的预构建函数,您可能会遇到此方法的问题。

这使我能够将所述组合类用作父类(或子类)(传递余下的 kwargs)。


1
我喜欢你的答案,但我会改成以下这样而不是循环遍历 inspect.signaturelist(inspect.signature(plt.scatter))。这会直接将其转换为列表。 - Coderji
1
@Coderji 感谢您的建议,这使得代码变得更短更易读。 - L. IJspeert

2

使用类来帮助

我遇到了这个问题,因为我需要做类似的事情。经过一些思考,似乎采用类的方法会对我有所帮助。我希望这也能帮助其他人。

import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d

class KWAs:
    def __init__(self, algo):
        self.algo = algo
        self.kwargs_dict = {
            'scatter_params':{},
            'plot_params':{}
        } # preloading group keys allows plotting when a kwarg group is absent.

    def add_kwargs_to_dict(self, group_name, **kwargs):
        self.kwargs_dict[group_name] = kwargs

    def list_kwargs(self):
        print('Listing all kwarg groups:')
        for kwargs in self.kwargs_dict:
            print('\tkwarg group {}: {}'.format(kwargs, self.kwargs_dict[kwargs]))
        print()

    def get_kwarg_group(self,group):
        print('kwarg group {}: {}'.format(group, self.kwargs_dict[group]))
        print()

    def smoothy(self, x,y, kind='cubic', order = 3):
        yn_cor = interp1d(x, y, kind=kind, assume_sorted = False)
        xn = np.linspace(np.min(x), np.max(x), len(x) * order)
        plt.scatter(x,y, **self.kwargs_dict['scatter_params'])
        plt.plot(xn, yn_cor(xn), **self.kwargs_dict['plot_params'])

        plt.show()

kwas = KWAs('LSQ')
N = 20
colors = np.random.rand(N)
area = (20 * np.random.rand(N))**2

kwas.add_kwargs_to_dict('scatter_params', s=area, c=colors, alpha=0.5)
kwas.add_kwargs_to_dict('plot_params', linewidth=2.0, color='r')
kwas.list_kwargs()
kwas.get_kwarg_group('scatter_params')
kwas.get_kwarg_group('plot_params')

x = []; y = []
for i in range(N):
    x.append(float(i)*np.pi/float(N))
    y.append(np.sin(x[-1]))

kwas.smoothy(x, y)

我不知道你想用kwargs控制哪些参数,所以我从matplotlib的例子中编造了一些。以上方法有效,并且您可以向类的kwargs字典添加无限数量的kwarg组,并添加其他方法,所有这些方法都可以根据需要使用kwargs。
以下是使用我添加的参数的输出: enter image description here

1
用稍微不同的方法,你可以使用partial。不要将函数参数化为将传递给其他函数(如scatterplot)的参数,而是将其参数化为这些其他函数本身的部分签名。
def scatter(a, b, z = 345): ...
def plot(d, c, z = 123): ...

def smoothy(x, y, scatter_fn = scatter, plot_fn = plot):
    a = f_a(x, y)
    b = f_b(x, y)
    # Use keyword args yourself so that it quickly errors out if the caller
    # provides those which you intend on explicitly providing.
    scatter_fn(a = x, b = y)
    plot_fn(c = a, d = b)

打电话的人如果对默认设置感到满意,就不会做太多事情。
smoothy(111, 222)

那些不行的人可以做什么:
from functools import partial

smoothy(111, 222, partial(scatter, z = 990), partial(plot, z = 991))

或许另一个优点是它可以提供更好的可测试性,因为现在您可以依赖注入模拟版本的scatter和/或plot函数。此外,无论如何,用户都必须了解在smoothy内部使用scatterplot的方法,因为您正在宣传kwargs属于这些函数的事实,所以将函数本身作为参数暴露出来可能并不是一个缺点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接