51Testing软件测试论坛

标题: Python编程中定义函数输入参数是不是很难? [打印本页]

作者: lsekfe    时间: 2022-11-8 11:44
标题: Python编程中定义函数输入参数是不是很难?
到目前为止,我们所看到的例子中,看到的所有参数都是普通的位置参数或关键字参数。也已经了解了如何将它们作为位置参数和关键字参数传递。关于它们没有太多要说的了,所以我们要来看看其他类别。在此之前,先来看看可选参数。
  1.可选参数
  除了我们在这里看到的类别之外,参数还可以分为必选项和可选项。可选参数有默认值,其值在函数定义中指定。语法是格式为:name=value。示例如下:
  1. <font size="3"># 定义参数有默认值的函数,调用时其为可选型参数

  2.   def func(a, b=4, c=88):

  3.       print(a, b, c)

  4.   func(1) # prints: 1 4 88

  5.   func(b=5, a=7, c=9) # prints: 7 5 9

  6.   func(42, c=9) # prints: 42 4 9

  7.   func(42, 43, 44) # prints: 42, 43, 44</font>
复制代码
这里,a是必需传递参数项,而b的默认值是4,c的默认值是88,两者是可选项。重要的是要注意,除了只有关键字的形参外,必需型形参必须始终位于函数定义中所有可选形参的左侧。试着在上面的例子中删除c的默认值,看看会发生什么。
  2.不定量位置参数
  有时,可能不希望为函数指定位置参数的确切数量,而Python通过使用可变位置参数提供了实现这一点的能力。让我们来看一个非常常见的用例,minimum()函数。
  这是一个计算其输入值最小值的函数,代码如下:

  1. <font size="3"># 不定量位置参数

  2.   def minimum(*n):

  3.       # print(type(n)) # n 是个元组

  4.       if n: #

  5.           mn = n[0]

  6.       for value in n[1:]:

  7.           if value < mn:

  8.               mn = value

  9.       print(mn)

  10.   minimum(1, 3, -7, 9) # n = (1, 3, -7, 9) - prints: -7

  11.   minimum() # n = () - prints: nothing</font>
复制代码


如上所见,当我们定义一个带有星号*的形参时,我们是在告诉Python,当函数被调用时,这个形参将收集数量不定的位置实参。在函数中,n是一个元组。可取消代码中的注释print(type(n))行,然后运行程序并看看输出。
  注意,一个函数最多只能有一个不定量位置参数——有更多的位置参数是没有意义的。Python将无法决定如何划分它们之间的参数。您也无法为变量位置参数指定默认值。默认值总是一个空元组。
  提示:
  是否注意到代码中是如何用一个简单的if n:检查n是否为空的?这是因为在Python中,集合对象在非空时求值为True,否则为False。元组、集合、列表、字典等等都是如此。
  另一件需要注意的事情是,当调用不带参数的函数时,可能想抛出一个错误,而不是静默地什么都不做。在这种情况下,我们不关心如何使这个函数健壮,而是要理解可变量位置参数。
  另外,是否注意到,定义不定量位置形参的语法与可迭代解包的语法非常相似?这并非巧合。毕竟,这两个特征互为镜像。它们也经常一起使用,因为不定量位置形参让你不必担心解包的可迭代对象的长度是否与函数定义中的形参数量相匹配。
  3.不定量关键字参数
  不定量关键字参数(Variable keyword parameters)非常类似于不定量位置参数。唯一的区别是语法(**而不是*)和它们以字典形式被收集的事实:



  1. <font size="3"># 不定量关键字参数

  2.   def func(**kwargs):

  3.       print(kwargs)

  4.   func(a=1, b=42) # prints {'a': 1, 'b': 42}

  5.   func() # prints {}

  6.   func(a=1, b=46, c=99) # prints {'a': 1, 'b': 46, 'c': 99}</font>
复制代码


如上所示,在函数定义的参数名称前添加**告诉Python使用该名称来收集数量不定的关键字参数。与不定量位置参数的情况一样,每个函数最多只能有一个可变关键字参数,并且不能指定默认值。
  就像可变量位置参数类似于可迭代解包一样,可变关键字参数类似于字典解包。字典解包也经常用于将参数传递给具有可变量关键字形参的函数。
  为什么能够传递可变数量的关键字参数如此重要,目前可能还不清楚,那么通过如何使用这一能力的示例,你将能更真实的理解其重要性。
  我们定义一个连接到数据库的函数:我们希望通过不带参数地调用这个函数来连接到默认数据库。还希望通过向函数传递适当的参数来连接到任何其他数据库。在你继续读下去之前,自己试着花几分钟自己想出一个解决方案:



  1. <font size="3"># 可变量关键字参数

  2.   def connect(**options):

  3.       conn_params = {

  4.                               'host': options.get('host', '127.0.0.1'),

  5.                               'port': options.get('port', 5432),

  6.                               'user': options.get('user', ''),

  7.                               'pwd': options.get('pwd', ''),

  8.                               }

  9.       print(conn_params)

  10.   # 然后连接数据库(注释掉的代码行)

  11.   # db.connect(**conn_params)

  12.   connect()

  13.   connect(host='127.0.0.42', port=5433)

  14.   connect(port=5431, user='admin', pwd='super')</font>
复制代码


注意,在函数中,我们可以准备一个连接参数字典(conn_params)使用默认值作为回退,其允许在函数调用时提供以覆盖它们。有更好的方法可以用更少的代码行来实现这一点,但我们现在不关心这一点。运行上述代码会得到以下结果:


  1. <font size="3"> {'a': 1, 'b': 46, 'c': 99}

  2.   {'host': '127.0.0.1', 'port': 5432, 'user': '', 'pwd': ''}

  3.   {'host': '127.0.0.42', 'port': 5433, 'user': '', 'pwd': ''}

  4.   {'host': '127.0.0.1', 'port': 5431, 'user': 'admin', 'pwd': 'super'}</font>
复制代码


注意函数调用和输出之间的对应关系,以及如何根据传递给函数的内容重写默认值。
  4.仅限位置参数
  从Python 3.8开始,PEP 570 (https://www.python.org/dev/peps/pep-0570/)引入了仅限位置的参数。有一种新的函数参数语法,/,表示一组函数形参必须在位置上指定,不能作为关键字参数传递。让我们看一个简单的例子:



  1. <font size="3"># 仅限位置参数

  2.   def func(a, b, /, c):

  3.       print(a, b, c)

  4.   func(1, 2, 3) # prints: 1 2 3

  5.   func(1, 2, c=3) # prints 1 2 3</font>
复制代码


在上面的例子中,我们定义了一个函数func(),它指定了三个参数:a、b和c。函数签名中的/表示a和b必须按位置传递,也就是说,不能通过关键字传递。
  示例中的最后两行显示,我们可以按位置传递所有三个参数来调用函数,或者可以按关键字传递c。这两种情况都可以正常工作,因为c定义在函数签名中的/之后。如果我们试图通过通过关键字传递a或b来调用函数,像这样:



  1. <font size="3"> func(1, b=2, c=3)</font>
复制代码


 这将产生如下类似回溯跟踪信息:


  1. <font size="3">Traceback (most recent call last):

  2.   File "……", line 9, in <module>

  3.   func(1, b=2, c=3)

  4.   TypeError: func() got some positional-only arguments passed as keyword arguments: 'b'</font>
复制代码


前面的例子告诉我们,Python现在反馈给我们调用func()的方式,其意思是:通过关键字传递了参数b,但不允许这样做。
  仅限位置参数也可以是可选的,如下所示:



  1. <font size="3"> # 可选的仅限位置参数

  2.   def func(a, b=2, /):

  3.       print(a, b)

  4.   func(4, 5) # prints 4 5

  5.   func(3) # prints 3 2</font>
复制代码


通过一些从官方文档中借来的例子来看看这个特性给该语言带来了什么。一个优点是能够完全模拟现有C编码函数的行为:


  1. <font size="3">def divmod(a, b, /):

  2.       "模拟内建函数 divmod()"

  3.       return (a // b, a % b)</font>
复制代码


另一个重要的用例是在形参名没有啥有意义的帮助的情况下排除关键字实参:


  1. <font size="3"> len(obj='hello')</font>
复制代码


在上面的例子中,obj关键字参数降低了可读性。此外,如果我们希望重构len函数的内部结构,并将obj重命名为the_object(或任何其他名称),更改保证不会破坏任何客户端代码,因为不会有任何对len()函数的调用,会涉及到现在已经过时的obj参数名称。
  最后,使用仅限位置形参意味着/左边的任何值都可以在不定量关键字实参中使用,如下例所示:



  1. <font size="3">def func_name(name, /, **kwargs):

  2.       print(name)

  3.       print(kwargs)

  4.   func_name('Positional-only name', name='Name in **kwargs')

  5.       # 打印输出为:

  6.       # Positional-only name

  7.       # {'name': 'Name in **kwargs'}</font>
复制代码


在函数签名中保留参数名以便在**kwargs中使用的能力可以生成更简单、更清晰的代码。
  现在来研究一下仅限位置类似版:仅限关键字参数。
  5.仅限关键字参数
  Python 3引入了仅限关键字的参数。我们只简要地研究它们,因为它们的用例并不常见。有两种方法可以指定它们,要么在不定量位置参数之后,要么在不定的*之后。来看两个例子。代码如下:



  1. <font size="3"># 仅限关键字参数

  2.   def kwo(*a, c):

  3.       print(a, c)

  4.   kwo(1, 2, 3, c=7) # prints: (1, 2, 3) 7

  5.   kwo(c=4) # prints: () 4

  6.   # kwo(1, 2) # 此行出问题——无效于法,并有如下错误

  7.   # TypeError: kwo() missing 1 required keyword-only argument: 'c'

  8.   def kwo2(a, b=42, *, c):

  9.       print(a, b, c)

  10.   kwo2(3, b=7, c=99) # prints: 3 7 99

  11.   kwo2(3, c=13) # prints: 3 42 13

  12.   # kwo2(3, 23) # 此行出问题——无效于法,并有如下错误

  13.   # TypeError: kwo2() missing 1 required keyword-only argument: 'c'</font>
复制代码


正如预期的那样,函数kwo()接受数量可变的位置参数(a)和一个只有关键字的关键字c。调用的结果很简单,你可以取消对第三个调用的注释,以查看Python返回什么错误。
  同样的情况也适用于函数kwo2(),它与kwo的不同之处在于,它接受一个位置参数a、一个关键字参数b和一个只有关键字的参数c。你可以取消对第三个调用的注释,以查看产生的错误。
  现在应已知道了如何指定不同类型的输入参数,接下来看看如何在函数定义中组合它们。
  6.组合输入参数
  可以在同一个函数中组合不同的参数类型(事实上,这样做通常非常有用)。就像在同一个函数调用中组合不同类型的实参一样,在顺序上有一些限制:
  ·仅限位置的参数放在前面,然后跟随一个斜杠“/”。
  · 普通参数在任何仅限位置参数之后。
  · 不定量位置参数在正常参数之后。
  · 只有关键字参数在不定量位置参数之后。
  · 不定量关键字参数总是排在最后。
  对于仅限位置参数和普通参数,任何必需的参数必须在任何可选参数之前定义。这意味着,如果你有一个可选的仅限位置参数,那么所有常规参数也必须是可选的。该规则不影响仅限关键字的参数。
  如果没有例子,这些规则可能会有点难以理解,所以来看几个示例:



  1. <font size="3"># 定义个带有所有参数形式的函数

  2.   def func(a, b, c=7, *args, **kwargs):

  3.       print('a, b, c:', a, b, c)

  4.       print('args:', args)

  5.       print('kwargs:', kwargs)

  6.   func(1, 2, 3, 5, 7, 9, A='a', B='b')</font>
复制代码


注意函数定义中参数的顺序。执行该程序会得到以下结果:


  1. <font size="3">a, b, c: 1 2 3

  2.   args: (5, 7, 9)

  3.   kwargs: {'A': 'a', 'B': 'b'}</font>
复制代码


现在再来看一个只有关键字参数的例子:


  1. <font size="3"># 仅限观自在参数

  2.   def allparams(a, /, b, c=42, *args, d=256, e, **kwargs):

  3.       print('a, b, c:', a, b, c)

  4.       print('d, e:', d, e)

  5.       print('args:', args)

  6.       print('kwargs:', kwargs)

  7.   allparams(1, 2, 3, 4, 5, 6, e=7, f=9, g=10)</font>
复制代码


注意,在函数声明中有仅限位置形参和仅限关键字形参:a仅限位置形参,而d和e仅限关键字形参。他们是在*args可变量位置参数之后,如果它们紧跟在单个*的后面,也会是一样的(在这种情况下,将没有任何可变位置参数)。运行程序会得到以下结果:


  1. <font size="3">a, b, c: 1 2 3

  2.   d, e: 256 7

  3.   args: (4, 5, 6)

  4.   kwargs: {'f': 9, 'g': 10}</font>
复制代码


另一件需要注意的事情是我们为可变量位置参数和关键字参数命名。你可以自由选择不同的名称,但请注意,args和kwargs是这些参数的常规名称,至少在一般情况下是这样。
  7.更多的签名示例
  为了简要回顾一下使用仅限位置和关键字说明符的函数签名,下面是一些进一步的示例。省略不定量位置和关键字参数,为简洁起见,我们只剩下以下语法:



  1. <font size="3">def xxxFuncName(positional_only_parameters, /,

  2.                                positional_or_keyword_parameters, *,

  3.                                keyword_only_parameters):

  4.       # 函数体

  5.       pass</font>
复制代码


首先,我们有仅限位置的参数,然后是位置或关键字参数,最后是仅限关键字参数。
  其他一些有效签名如下:



  1. <font size="3">def xxxFuncName(p1, p2, /, p_or_kw, *, kw):

  2.   def xxxFuncName(p1, p2=None, /, p_or_kw=None, *, kw):

  3.   def xxxFuncName(p1, p2=None, /, *, kw):

  4.   def xxxFuncName(p1, p2=None, /):

  5.   def xxxFuncName(p1, p2, /, p_or_kw):

  6.   def xxxFuncName(p1, p2, /):</font>
复制代码


以上均为有效签名,下列为无效签名:


  1. <font size="3">def xxxFuncName(p1, p2=None, /, p_or_kw, *, kw):

  2.   def xxxFuncName(p1=None, p2, /, p_or_kw=None, *, kw):

  3.   def xxxFuncName(p1=None, p2, /):</font>
复制代码


提示:在这一点上,要很好的理解与掌握,一个有用的练习方法是实现上述示例签名中的任何一个,打印出这些参数的值,就像我们在前面的练习中所做的那样,并以不同的方式传递参数。
  8.避免陷阱!可变默认值
  要注意的一件事是,在Python中,默认值是在定义时创建的;因此,根据默认值的可变性,对同一函数的后续调用可能会有不同的行为。让我们看一个例子:



  1. <font size="3"> # 带有可变默认值参数函数

  2.   def func(a=[], b={}):

  3.       print(a)

  4.       print(b)

  5.       print('#' * 12)

  6.       a.append(len(a)) # 影响a的默认值

  7.       b[len(a)] = len(a) # 影响b的默认值

  8.   func()

  9.   func()

  10.   func()</font>
复制代码


两个参数都有可变的默认值。这意味着,如果执行中影响了这些对象,任何修改都将停留在后续的函数调用中。看看你是否能理解这些调用的输出:


  1. <font size="3"> []

  2.   {}

  3.   ############

  4.   [0]

  5.   {1: 1}

  6.   ############

  7.   [0, 1]

  8.   {1: 1, 2: 2}

  9.   ############</font>
复制代码


是不是很搞事?虽然这种行为一开始看起来很奇怪,但它实际上是有意义的,而且非常方便——例如,当使用“记忆”技术时,就有了天生之才的傲娇。更有趣的是,在调用之间,我们引入了一个不使用默认值的函数,比如:


  1. <font size="3"> # 中间调停者调用

  2.   func()

  3.   func(a=[1, 2, 3], b={'B': 1})

  4.   func()</font>
复制代码


 运行代码输出内容如下所示:


  1. <font size="3"> []

  2.   {}

  3.   ############

  4.   [1, 2, 3]

  5.   {'B': 1}

  6.   ############

  7.   [0]

  8.   {1: 1}

  9.   ############</font>
复制代码


这个输出告诉我们,即使使用其他值调用函数,默认值也会被保留。我想到的一个问题是,如何每次都获得一个新的空值?惯例是这样的:


  1. <font size="3"># 无陷阱可变缺省默认值

  2.   def func(a=None):

  3.       if a is None:

  4.           a = []

  5.       # 干些使用a的工作 ...</font>
复制代码


注意,通过使用前面的技术,如果调用函数时没有传递a,我们总是得到一个全新的空列表。






欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2