【问题标题】:What's the pythonic way to pass arguments between functions?在函数之间传递参数的pythonic方式是什么?
【发布时间】:2017-03-24 15:26:59
【问题描述】:

我从用户那里获取了一些参数,并通过函数传递给函数(每个函数在不同的类中),直到它最终到达一个执行某些处理的函数,然后将解决方案返回到链上。在链上,函数变得越来越抽象,合并了下层函数多次运行的结果。

我应该在哪里使用*args 和**kwargs?

我认为*args 和*kwargs 可以用于函数不显式使用参数的每个函数。但是,实际参数需要在top_level 中定义,以便用户知道函数期望什么。

我应该在哪里定义输入的含义?

我认为它们应该在top_level 中定义,因为这是最终用户可能希望查看文档的那个。

我应该在哪里定义哪些默认值?

我认为它们应该在top_level 中定义,因为这是最终用户与之交互的地方。

这是一个演示参数传递的简单示例,我没有展示函数如何变得越来越抽象或它们如何与不同的类交互,因为我觉得这是不必要的细节。

def top_level(a=1, b=1, c=1, d=1, e=1):
    """ Compute sum of five numbers.
    :param a: int, a
    :param b: int, b
    :param c: int, c
    :param d: int, d
    :param e: int, e
    :return: int, sum
    """
    return mid_level(a, b, c, d, e)


def mid_level(*args, **kwargs):
    return bottom_level(*args, **kwargs)


def bottom_level(a, b, c, d, e):
    return a + b + c + d + e

print top_level(1, 2, 3)
8

有这样的传递参数的 Python 约定吗?

【问题讨论】:

  • 我知道您正在尝试做一个例子,但很难想象一个实际的用例是因为mid_level 目前没有任何用途。事实上,从这个角度来看,bottom_level 也不是。
  • @Julien 是的,在真实环境中,top_level 可能是用户与之交互的模块,mid_level 是核心模块,而“bottom_level”是描述数据结构的模块。
  • 用户界面,如从命令行通过用户输入?还是来自命令参数?
  • 来自 python 控制台。用户可能会说import my_module,然后是print top_level(1, 2, 3)。

标签: python coding-style


【解决方案1】:

我不会回答您的问题,因为这就像回答“使用螺丝刀拧紧螺母的最佳方法是什么?”的问题。 IE。我不相信您寻求指导的工具(*args 和 **kwargs)旨在解决您想要解决的问题。

我会回答这个问题:“如何将一些数据与一组函数关联起来?”,答案显然是使用类。

欢迎来到面向对象编程。我想你会喜欢的!

这是我的意思的一个非常基本的示例,但是由于它很简单,因此很难从示例中确切知道您想要什么,但基本原理是将您的数据封装在一个类中,然后使用类的方法。

  • 然后您可以在类中的方法之间调用,而无需一直传递大量参数(例如下面的.calculate() 方法),您不知道是顶层需要还是底层需要.
  • 您可以将参数记录在一个地方,即__init__ 方法。
  • 您可以通过对代码透明的子类化进行自定义(因为如果您覆盖子类中的方法,它仍然可以被更通用的超类使用),正如我在下面为 .reduce(x, y) 方法所做的那样。李>

例子:

class ReductionCalculator:

    def __init__(self, *args):
        self.args = args

    def calculate(self):

        start = self.args[0]
        for arg in self.args[1:]:
            start = self.reduce(start, arg)

        return start


class Summer(ReductionCalculator):

    def reduce(self, x, y):
        return x + y


class Multiplier(ReductionCalculator):

    def reduce(self, x, y):
        return x * y

summer = Summer(1, 2, 4)
print('sum: %d' % (summer.calculate(),))
multiplier = Multiplier(1, 2, 4)
print('sum: %d' % (multiplier.calculate(),))

【讨论】:

  • 我已经使用了passed along function to function (each function in a different class) 的课程,我很喜欢它们。在这种情况下,我传递参数的函数已经在不同的类中。
  • 好的,我知道我并没有真正从你的问题中理解这一点......但如果可能的话,你认为你可以提供一个更好的例子来说明你想要实现的目标吗?目前,您获得的示例没有多大意义(甚至不使用类),因此很难知道您的意图。目前对我来说的关键问题是我无法想象你的界面。可能我只是想回答一个你觉得你没有问过的问题,在这种情况下请忽略我,但目前它对我来说真的不合适!
【解决方案2】:

这个方法怎么样:创建一个类,命名为AllInputs,它代表所有“从用户那里获取的参数”的集合。此类的唯一目的是充当一组值的容器。当然,这个类的一个实例在程序的顶层被初始化。

class AllInputs:
   def __init__(self,a=1, b=1, c=1, d=1, e=1):
    """ Compute sum of five numbers.
    :param a: int, a
    :param b: int, b
    :param c: int, c
    :param d: int, d
    :param e: int, e
    """
    self.a = a
    self.b = b
    self.c = c
    self.d = d
    self.e = e

这个对象,称为all_inputs,现在作为单个参数传递给示例中的所有函数。如果一个函数不使用对象中的任何字段,那很好;它只是将它传递给完成实际工作的低级函数。要重构您的示例,您现在将拥有:

def top_level(all_inputs):
    """ Compute sum of all inputs
    :return: int, sum
    """
    return mid_level(all_inputs)


def mid_level(all_inputs):
    return bottom_level(all_inputs)


def bottom_level(all_inputs):
    return (all_inputs.a + all_inputs.b + all_inputs.c + 
      all_inputs.d + all_inputs.e)

all_inputs = AllInputs(1, 2, 3)
print top_level(all_inputs)
8

我不知道这是“Pythonic”还是“non-Pythonic”,我不在乎。我认为将程序将使用的数据组合在一起是一个很好的编程理念。初始化过程将默认值与用户获取的其他值相结合,集中在一个易于理解的地方。这是合理的自我记录。你说函数调用分布在几个类中,这没问题。函数调用很干净,程序流程很容易遵循。通过将一些计算放在AllInputs 中可以进行优化,这样您就可以避免重复代码。

我不喜欢你的例子(我认为你也不喜欢它,或者你可能一开始就不会问这个问题)是它如何使用*args 语法。当我看到这种语法时,我认为所有参数都具有相同的语义,就像在标准库函数os.path.join 中一样。在您的应用程序中,如果我理解这个问题,那么低级函数要求参数列表按特定顺序排列并具有特定含义(您的示例没有反映这一点,但文本暗示了这一点)。看到作为*args 传递给函数的参数,然后在较低级别,它们的特定名称和含义再次出现,这令人困惑。将它们分组到一个对象中可以清楚地知道发生了什么。

【讨论】:

  • 我认为这是一针见血。但是,如果 AllInputs 类仅用作容器,我认为字典也可以达到目的。也许要明确指出的最后一个好处是,当使用您描述的方法时,对 all_inputs 中值的任何更改(无论是类还是字典)都可以轻松地在各个级​​别上下传播。
  • 我有时会使用小类,以防字典也能正常工作。这有时允许静态代码检查工具(我使用 pylint)在我输入错误的变量名时捕获错误。它不能用字典来做到这一点。
【解决方案3】:

这不是最常见的模式,但我已经在具有嵌套命令级别的命令行程序中看到它:子命令、子子命令等。这是一个模型,其中“上层”函数可能或多或少是调度程序,并且没有关于给定路由内的子函数需要哪些参数的信息。这个模型最纯粹的场景是子命令是插件,而“上层”实际上没有关于子功能的信息,除了插件应该遵守的调用约定。

在这些情况下,我认为 Python 的方式是将参数从高级函数传递到低级函数,并让工作人员级别决定哪些是有用的。可能的参数范围将在调用约定中定义。这是基于 DRY 的 Pythonic——不要重复。如果低级/worker 函数定义了哪些输入是必需的或可选的,那么在更高级别不重复此信息通常是有意义的。

对于任何控制反转的流程设计都可以这样说,而不仅仅是带有插件的 CLI 应用程序。有许多应用程序设计我不会使用这种方法,但它在这里有效。

输入的含义必须设置在它可能出现的最高级别——作为较低级别的接口规范(约定,而不是编程)。否则输入将没有语义。

如果一个输入可以被多个子功能使用,即控制流中有一个链接或管道概念,那么输入的默认值也需要在输入的最顶层定义。

【讨论】:

    【解决方案4】:

    我认为将参数传递到多个函数级别本身并不是 Python 语言。

    来自 Python 之禅:

    • 简单胜于复杂
    • 平面优于嵌套

    编辑:

    如果有很多参数并且中间的函数只是传递它们,我可能会将它们包装在 tuple 中并在最低级别解开它们。

    【讨论】:

    • 但是,同时函数应该简短并且只做一件事。我正在使用多个级别的函数,因为在每个级别上,该函数都会做一些更抽象的事情,因为它在更抽象的类上运行。而且,碰巧我需要在它们之间传递相同的属性。
    • 你的问题的表述“直到它最终得到一个实际执行某些处理的函数”暗示中间的函数几乎没有任何作用。
    • 啊,我明白了,我会改写它。
    • ...将它们包装在一个元组中...让我们称之为“上下文”并将所有语义抛诸脑后。很抱歉,这听起来很苛刻,但是当遇到此类问题时,我会立即开始重构。
    猜你喜欢
    • 1970-01-01
    • 2011-07-30
    • 1970-01-01
    • 1970-01-01
    • 2021-04-28
    • 2016-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多