什么是在函数之间传递参数的pythonic方法?

时间:2017-03-24 15:26:59

标签: python coding-style

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

我应该在哪里使用*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约定?

4 个答案:

答案 0 :(得分:6)

我不会回答你的问题,因为这就像回答问题"什么是使用螺丝刀拧紧螺母的最佳方法?"。即我不相信您要求的工具(*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(),))

答案 1 :(得分:3)

这种方法怎么样:创建一个类,称之为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"或者"非Pythonic"而且我不在乎。我认为将程序将使用的数据组合在一起是一个很好的编程思路。初始化过程将默认值与从用户获取的其他值组合在一起,集中在一个易于理解的地方。它是合理的自我记录。你说函数调用分布在几个类中,这没问题。函数调用是干净的,程序流程很容易遵循。通过在AllInputs中放置一些计算可以进行优化,这样可以避免重复代码。

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

答案 2 :(得分:2)

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

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

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

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

如果输入可以被多个子函数使用,即控制流中存在链接或流水线概念,那么输入的默认值也需要在输入的最高级别定义。

答案 3 :(得分:1)

我认为将参数传递到几个级别的函数本身并不是pythonic。

来自Python的禅宗:

  • 简单比复杂
  • 更好
  • Flat比嵌套
  • 更好

修改

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