理解Python中的位置参数

问题描述 投票:-2回答:4

对于以下Python脚本:

from sys import argv

script, input_encoding, error = argv

def main(language_file, encoding, errors):

    line = language_file.readline()

    if line:

        print_line(line, encoding, errors)
        return main(language_file, encoding, errors)

def print_line(line, encoding, errors):
    next_lang = line.strip()
    raw_bytes = next_lang.encode(encoding, errors=errors)
    cooked_string = raw_bytes.decode(encoding, errors=errors)

    print(raw_bytes, "<===>", cooked_string)

languages = open("languages.txt", encoding="utf-8")

main(languages, input_encoding, error)

看看main函数我不明白以下几行:

print_line(line, encoding, errors)

为什么我们调用print_line函数并将参数传递给它的名称与其参数完全相同?

print_line()

当我尝试调用print_line()参数而不传递参数时,Python正在输出:

print_line()缺少3个必需的位置参数:'line','encoding'和'errors'

python python-3.x
4个回答
1
投票

OP:print_line()缺少3个必需的位置参数:'line','encoding'和'errors'

错误很明显,因为它是print_line()函数的定义方式。

此外:

def print_line(line, encoding, errors):
    print(line, encoding, errors)


line = 1
encoding = 2
errors = 3

print_line(errors, encoding, line)

OUTPUT:

3 2 1

注意:它是位置参数,而不是命名参数

编辑:1

def abc(a,b,c=2):

return a+b+c

abc(1,2) #both positional argument and c is default

5

abc(2, b=3) # positional, named and again c is default

7

abc(a=2,b=4) # both named argument and c is default

8

编辑2:

OP:请问位置论证的目的是什么?

好 ..

简短回答:位置参数是任何不作为key=value对提供的参数。

不幸的是,要理解这意味着什么。

术语“参数”在整个编程社区中使用得有点不精确,特别是在Python文档中。

技术上,参数是您传递给函数的参数,参数是您定义为这些参数的名称/占位符的参数。

所以,当我定义一个函数时:

def foo(a,b):
    return a+b

......并像这样称呼它:

foo(1,3)

...然后a和b是我的参数,而1和3是给定函数调用的参数。

现在这是一个狡辩。当人们实际上是在执行函数时将包含参数的名称(参数)时,人们通常会将a和b称为其函数的“参数”。

现在,有了这一点,理解Python支持四类参数:“required position”(你看过几乎任何其他编程语言的那种),“可选”......或“默认”......位置指定了一些默认值的参数,“star args”(类似于其他一些语言中的VARARGS支持,如C / C ++和Java),以及“kwargs”。

后者几乎是Python独有的(虽然Ruby有非常相似的支持)。

因此,您可以使用参数列表定义函数,如:

def bar(a, b, c=None, d=[], *e,  **opts):
    '''bar takes all sorts of arguments
    '''
    results = dict()
    results['positional 1'] = a
    results['positional 2'] = b
    results['sum']=a+b
    results['optional'] = list()
    for each in e:
        results['optional'].append(each)
    if c is not None:
        results['default over-ridden']=c
    else:
        results['default']='no arg supplied for parameter c'
    d.append(results)
    if 'verbose' in opts and opts['verbose']:
        for k,v in results:
            print '%s:%s' % (k, v)
    return (results, d)

...这个相当人为的例子有一些正常的,传统的位置参数(a和b),以及可选的第三和第四个参数(如果调用bar(),其中一个将默认为特殊的Python单例值None只有两个参数,另一个默认为列表)以及一个可选的可变数量的附加参数(通过名为“e”的参数传递给我们的函数),最后,bar()可以接受任意数量的“keyword”参数(作为键值对传递给它并通过参数“opts”引用(在我的示例中)。

那里有很多要消化的东西。首先是a和b。这些就像你在大多数编程语言中看到的那样。如果仅使用一个参数调用它,则会出现错误,因为该函数需要两个参数。然后有c和d ...这些参数可以提供参数...但是如果调用函数只有两个必需参数而不是参数c将引用“无”,d将引用列表...这是在我们定义函数时实例化的!

哇!重新阅读最后一点,因为对于那些错误地使用默认为可变类型(列表,字典,集合或自定义类的大多数实例)的参数定义函数的人来说,这是一个常见的混淆源。

当您在Python中定义函数时,解释器正在执行代码。它正在执行def语句并评估参数(它们成为函数的参数)。因此,当定义函数时,Python虚拟机会实例化一个列表([] ---空列表文字)。现在,参数(在我的示例中为e)绑定到该列表,就像任何Python“变量”(名称)绑定到任何其他对象一样。并且只要有三个或更少的参数调用bar(),就可以访问它引用的对象。

这是一个棘手的问题:每当你用超过三个参数调用bar时,参数e将被绑定(对于特定调用的持续时间)到第四个参数。基础列表将在该调用期间隐藏。这样的默认列表对象包含在闭包中,并且通常在函数外部是完全不可访问的。 (在这个例子中,我在return语句中包含了对它的引用,显示了如果我们选择,我们如何导出对该封闭对象的引用)。

因此,如果我传递bar()四个参数,那么它将尝试使用该对象的'append'方法修改通过其“e”参数引用的对象。 (显然,这将失败,并为任何不支持append方法的对象引发异常)。每次我只用三个参数调用bar(),然后我在其闭包中修改该封闭的列表对象。

这可能很有用,但很少有新程序员在学习Python时所期望的。因此,作为规则,不要将具有可变对象的函数定义为参数的默认值。当你很好地理解语义时,你就知道什么时候打破这个规则。

出于某种原因,我使用“无”作为其他参数的默认值。对于要为其设置默认值的任何参数,使用“None”作为默认值通常很有用,然后,明确测试“is None”并在函数体内提供自己的默认值。通过这种方式,您可以区分默认参数值和显式传递给您的任何参数(并且恰好与您的默认值相匹配)。 (这也将防止您可能无意中创建如前所述的可变闭包的情况。在函数体中发生的赋值/绑定将导致每个达到该条件的调用的新实例化)。

因此,我们已经涵盖了所需的位置参数,以及那些提供默认值(因此,可选的位置参数)。

使用参数'e',我们看到Python支持可变数量的参数。在第四个之后指定的任何参数都将被收集到一个元组中并通过我们的参数'e'传递给我们...除了形式的任何参数:this = that。

这终究带给我们了。 “选择”。传统上,Python程序员将此参数称为“kwargs”(键/字参数)。我将其命名为“opts”,以强调“kwargs”只是传统的术语,从技术上来说,有点令人困惑,因为我在本文中已经提到过,它是一个参数,它指的是任何关键字参数。可能已通过某些函数调用作为参数传递。

可以编写所有函数,使得它们不采用任何位置参数,并且仅使用“key / words arguments”参数定义。然后,您可以确保函数的调用者必须始终说明每次调用时哪个参数绑定到哪个名称。如果你的函数在错误的顺序中使用参数调用的任何情况下可能会产生灾难性后果,这可能很方便。

我意识到这很令人困惑......我绝对建议您使用不同的函数定义和各种调用来查看这些是如何交互的。

我还会注意到另外一个可以咬你的“gotchya”。您的调用者无法使用名称与您的参数名称冲突的键传递kwargs(opts)值。尝试使用参数列表调用bar(),例如:bar(1,2,3,4,a=99),你将得到如下例外:"TypeError: bar() got multiple values for keyword argument 'a'"

Python通过管理命名空间(如字典)将参数解析为参数。试图提供一个键/单词参数,其中任何与您的参数名称匹配的键都会产生歧义......从而引发异常。

我还要为这个已经很麻烦的答案添加两个额外的注释。

当您在Python中调用函数时,您可以传递这样的参数:

myargs=(1,2,3)
bar(*myargs)

...这将被视为你用bar(1,2,3)调用它 - 或者好像你做了以下函数调用:apply(bar, myargs)

事实上,过去你必须使用apply()函数来完成这个间接层(当你可以用*foo参数定义函数但不用*foo参数调用它们时)。

(在函数调用中添加* args语法很大程度上取代了Python的apply()内置函数)。

...最后,可以使用以下语法传递kwargs的字典:

mykwargs={'z':99, 'whatever':'yikes'}
bar(1,2,3, **mykwargs)

...或者与我之前的例子相结合:

bar(*myargs, **mykwargs)

因此,理解定义函数时*和**的含义以及调用它们时的含义之间的区别至关重要。如果您理解参数和参数之间的区别(尽管术语“参数”更常用),这些含义是相互补充和直观的。


0
投票

为什么我们调用print_line函数并将参数传递给它的名称与它的参数完全相同?

这真的只是一个巧合。以下与您的示例完全相同:

from sys import argv

script, input_encoding, error = argv

def main(language_file, what_encoding_do_you_want, list_of_errors):

    next_line = language_file.readline()

    if next_line:

        print_line(next_line, what_encoding_do_you_want, list_of_errors)
        return main(language_file, what_encoding_do_you_want, list_of_errors)

def print_line(line, encoding, errors):
    next_lang = line.strip()
    raw_bytes = next_lang.encode(encoding, errors=errors)
    cooked_string = raw_bytes.decode(encoding, errors=errors)

    print(raw_bytes, "<===>", cooked_string)

languages = open("languages.txt", encoding="utf-8")

main(languages, input_encoding, error)

这一切都归结为“范围”。 print_line的函数定义声明它有三个(位置)参数,可以在函数内部称为“行”,“编码”和“错误”。

从main调用print_line函数会向调用中添加三个参数,这些参数引用代码中该点的现有变量或参数:line指的是在那里创建的变量; encoding和encoding是指main函数本身的参数。


0
投票

该函数需要三个参数,通常,您按指定的顺序提供它们。

Python允许您以任何您喜欢的顺序传递它们,但是:

print_line(encoding='ascii', line='hello', errors=None)

甚至

my_dict = {
    'line': 'privet, mir!',
    'errors': None,
    'encoding': 'utf-8'
}
print_line(**my_dict)

但是,该函数需要所有这些参数;你必须以某种方式传递它们,否则你会得到一条错误信息,就像你注意到的那样。

变量的范围是局部的:def中的任何内容都是它定义的函数的局部变量,并且与该块之外的任何其他具有相同名称的变量分开。

通过定义默认值,可以使参数成为可选参数。可选参数必须始终位于Python中的必需参数之后。

def another_fun(confetti, fireworks, bassoons=None, candy='sugar fluff')

如果你只用两个参数调用another_funbassoons默认为Nonecandy将设置为字符串'sugar fluff'


-1
投票

我们为什么要调用print_line函数

好吧,显然执行它;-)

并将参数传递给它

因为函数被定义为接受参数 - 如果你不提供它需要的工作,它就无法完成它的工作。

名称与其参数完全相同?

这实际上是完全无关紧要的 - 它恰好发生在main()中的变量被命名为与函数的参数相同(这是有道理的,因为我们对相同的东西 - 一个“文本行”,一个编码名称和一个值描述了如何处理编码错误),但是您只传递任意名称的litteral(未命名)值或变量,它可以正常工作。

功能是(大多数)独立的工作单元。它通常使用一些内部(“局部”)变量,只有它可以看到并且只在函数执行期间存在。它通常也需要参数:必须由调用者传递的值,并绑定到匹配的参数名称(在我们的例子中,第一个值将绑定到line名称,第二个值绑定到encoding名称等)。本地名称(局部变量和参数)与调用者范围内已知这些值的名称完全无关(即使它们绑定到名称 - 正如我所说,您可以传递文字值或其他匿名对象)

当我尝试在不传递参数的情况下调用print_line()参数时,Python输出“print_line()缺少3个必需的位置参数:'line','encoding'和'errors'”

当然是。该函数需要三个参数,因此您必须传递三个参数,简单和简单。在调用者范围(main函数)中有三个相同名称的局部变量这一事实不会自动为您填写“填充”这些参数,并且print_line函数无论如何都不知道它的调用者范围。

请注意,术语“位置”和“命名”参数主要是指如何传递参数本身 - 按位置(默认值),我已经在上面解释过,或者通过名称(即print_line(line="hello", errors="ignore", encoding="utf-8"),它允许您传递参数)以不同的顺序,或者你动态构建的词典等等,但是你需要先了解函数,参数和范围的概念才能进一步...

我强烈建议你做官方教程,它确实有a chapter on functions - 它主要用于已经有一些编程经验的人(它不是CS101课程),但它仍然得到了很好的解释。

© www.soinside.com 2019 - 2024. All rights reserved.