标题4更多的控制流工具(2)py导引 编译之五

4更多的控制流工具(2) 编译之五

标题4.7. 定义函数中的更多内容

用一些参数的可变数来定义函数,这也是可能的。有三种形式,这三种形式也可以组合起来。
.

标题4.7.1. 缺省参数值default Argument Values

最有用的定义参数形式是对一个或者多个参数,指派缺省值default value。这样创建的函数,和这个函数允许定义的参数相比,可以用比较少的参数被调用,例如:

在这里插入代码片
>>> def ask_ok(prompt, retires=4, reminder='Plesse try again'):
...     while True:
...         ok = input(prompt)
...         if ok in ('y', 'ye', 'yes'):
...             return True
...         if ok in ('n', 'no', 'nop', 'nope'):
...             return False
...         retires = retires - 1
...         if retires < 0:
...             raise ValueError('invalid user response')
...         print(reminder)

这个函数可以用几种方式调用:
第一种方式,仅给定强制参数:ask_ok(‘Do you really want to quit?’)
第二种方式,可选参数之一:ask_ok(‘Ok to overwrite the file?’, 2)
第三种方式,甚至给定所有的参数:ask_ok(‘Ok to overwrite the file?’ 2, ‘come on, only yes or no!’)
这个实例也引入了关键字in。这个关键字测试了一个序列是否含有某个给定的值。
缺省值在函数定义范围内,该函数的某个点位上被赋值。所以有:

在这里插入代码片
>>> i=5
>>>
>>> def f(arg=i):
...     print(arg)
...
>>> i=6
>>> f()
5
>>> i=7
>>> f()
5

有点奇怪的是,你好像把i赋值换成别的什么数,6也好,7也好,结果打印出的还是5。以下的警示,大概解释了这个疑问。
重要的警示:缺省值只能赋值一次。当缺省值是一个可变的对象,例如一个列表,一个词典或者一个大多数布尔类中的实例的时候,这会造成一些差异。例如,以下函数就累积了它每次被调用后通过了的参数:

在这里插入代码片
>>> def f(a, L=[]):
...     L.append(a)
...     return L
...
>>> print(f(1))
[1]
>>> print(f(2))
[1, 2]
>>> print(f(3))
[1, 2, 3]
>>> def f(a, L=[]):
...     L.append(a)
...     return L
... print(f(1))
  File "<stdin>", line 4
    print(f(1))
    ^
SyntaxError: invalid syntax
>>> print(f(1), f(2), f(3))
[1, 2, 3, 1, 2, 3] [1, 2, 3, 1, 2, 3] [1, 2, 3, 1, 2, 3]
>>>

打印的结果立刻就出来了,你想省掉几个print,结果成了一个列表的集成。这个python符号的熟悉,还真需要一个过程。
如果你不想这个缺省在随后的一些调用中被共享,你可以这样来编写你这个函数:

在这里插入代码片
>>> def f(a, L=None):
...     if L is None:
...         L = []
...     L.append(a)
...     return L

标题4.7.2. 关键字参数Keyword Arguments

函数也可以使用关键字参数的方式被调用,这个关键字参数形式是kwarg=value。例如以下函数:

在这里插入代码片
PS C:\Users\lenovo> py
Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def parrot(voltage, stage='a stiff', action='voom', type='Norweqian Blue'):
...     print("--This parrot wouldn't",action, end=' ')
...     print("if you put", voltage, "volts through it.")
...     print("--Lovely plumage, the", type)
...     print("--It's", state, "!")

接受一个要求的参数voltage和三个可选参数state, action,与type,这个函数可以用以下方式中的任意一种被调用。按照这一节的内容,先把那个有关键字参数的代码打出来。这是一个功夫活,很容易出错。出错还很麻烦。在python解释器中出错一点就前功尽弃,错了很多次。突然想到还是换到atom的那个编辑器中去敲代码,然后到python中执行恐怕要方便一些。于是换到atom编辑器。一个一个执行那些代关键字参数的调用。果然有效。执行那个parrot(1000)的函数调用就成功了,虽然犯了标识符缩进的错误,但很快得到纠正,于是有以下函数代码和执行结果:

在这里插入代码片
def parrot(voltage, state='a stiff', action='voom', type='Norweqian Blue'):
    print("--This parrot wouldn't", action, end='')
    print("if you put", voltage, "volts through it.")
    print("--Lovely plumage, the", type)
    print("--It's", state, "!")
parrot(1000)

执行结果

在这里插入代码片
IndentationError: expected an indented block
PS C:\Users\lenovo\desktop\python翻译文件夹\python导引翻译代码文件夹> py 4.7kw.py
  File "4.7kw.py", line 2
    print("--This parrot wouldn't", action, end='')
    ^
IndentationError: expected an indented block
PS C:\Users\lenovo\desktop\python翻译文件夹\python导引翻译代码文件夹> py 4.7kw.py
--This parrot wouldn't voomif you put 1000 volts through it.
--Lovely plumage, the Norweqian Blue
--It's a stiff !

再来两个执行结果
两个关键字参数的执行结果

在这里插入代码片
PS C:\Users\lenovo\desktop\python翻译文件夹\python导引翻译代码文件夹> py 4.7kw.py
--This parrot wouldn't VOOOOOOMif you put 1000000 volts through it.
--Lovely plumage, the Norweqian Blue
--It's a stiff !

可以验证,三个关键字参数的执行结果,一个位置参数和一个关键字参数的执行结果等等,全都是一样。
但以下的调用指令则全是无效指令:

在这里插入代码片
>>> parrot()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'parrot' is not defined
>>> parrot(voltage=5.0, 'dead')
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
>>> parrot(110, voltage=220)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'parrot' is not defined
>>> parrot(actor='John Cleese')  # 未知参数

在一个函数调用中,关键字参数必须跟随位置函数positional function。所有的关键字参数要想通过,必须匹配被这个函数接受的参数之一(例如actor对于parrot这个函数就不是有效的函数),参数的次序不重要。这也包括非可选函数,也必须是有效的)。没有参数可以收到一次以上的值。这里有一个因为这个限制,而无法通过的参数实例。

在这里插入代码片
SyntaxError: invalid syntax
>>> def function(a):
...     pass
...
>>> function(0, a=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for argument 'a'

当有一个形式为双星号name的最后形参formal parameter出现的时候,它收到一个词典(参看映射类型:词典Mapping Types-dict)该词典含有所有的关键字参数,除了那些对应于形参的之外。这也许会与形式为单星号name的形参相组合(这一点将在下一个子节中描述),这个形参会收到一个元组,元组中含有在形参列表之外的位置参数。(单星号的*name必须出现在双星号的**name之前)例如,如果我们定义像以下类型的函数:

在这里插入代码片
IndentationError: expected an indented block
>>> def cheeseshop(kind, *argument, **keywords):
...     print("--Do you have any", kind, "?")
...     print("--I'am sorry, we're all out of". kind)
...     for arg in arguments:
...         print(arg)
...     print("-" * 40)
...     for kw in keywords:
...         print(kw, ":", keywords[ky])

这个函数可以像这样被调用:

在这里插入代码片
>>> cheeseshop("Limburger", "It's very runny, sir.",
...            "It's really very, VERY runny, sir.".
...            shopkeeper="Michael Palin",
...            client="John Cleese",
...            sketch="Cheese Shop Sketch")

#在arom中始终提醒有语法错误,检查多次查不出来。去掉后面的三个等式,才有打印的信息。它打印出的就是以下结果。
#问题在什么地方还没有找到,暂且到此吧。

在这里插入代码片
def cheeseshop(kind, *arguments, **keywords):
    print("--Do you have any", kind, "?")
    print("--I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    for kw in keywords:
        print(kw, ":", keywords[kw])

cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.")

注意:其中关键字参数打印出的次序保证了匹配它们在函数调用中提供的次序。但因为始终出现语法错误,我们去掉了后面三个个名称参数的结果,所以这种匹配也就没有感觉到。注意在后面找到这种感觉。

标题4.7.3. 特殊参数Special parameters

借助缺省,参数可能通过一个python函数,或者可以借助位置或者借助关键字来通过。因为可读性和操作,这就构成了意义去限定参数能够通过的方式,由此而使得开发者仅需要看看函数定义去判定项目是否依据位置,依据位置或者关键字,或者依据关键字来通过的。
一个函数定义也许是像这样的:
在这里插入图片描述

左斜杠/和星号*地方可选。若需使用左斜杠/和星号,这些符号依据这些参数如何通过该函数来指示参数类别:有仅仅位置的参数,有位置或者关键字的参数和仅仅关键字的参数。关键字参数也被作为命名的参数提及到。

标题4.7.3.1. 位置或者关键字参数Positional-or-Keyword Arguments

如果左斜杠符号/和星号*不在函数定义中出现,参数可能依据位置或者依据关键字通过函数。

标题4.7.3.2. 仅位置参数Positional-Only Parameters

稍微详细一点地来看这一段,把某种参数标记为仅仅位置的参数这是很有可能的。如果是仅仅位置参数,该参数的次序就是个事,这类参数不可能使用关键字通过。仅仅位置参数常置于一个左斜杠/之前。这个斜杠用来把仅仅位置参数和别的参数区分开来。如果函数定义中没有这个斜杠/,那就表示该函数也就没有仅仅位置参数。
跟随这个斜杠/后的参数也许是位置或者关键字参数,或者是仅仅关键字参数。

标题4.7.3.3.仅关键字参数 Keyword-Only Arguments

把参数标记为仅仅关键字参数,那就是指示,这个参数必须用关键字参数才能通过,这需要把星号*置于参数列表之中,恰好位于第一个仅仅关键字参数之前。

标题4.7.3.4. 函数实例Function Examples

思考以下函数定义实例,特别地关注那两个标注符号,斜杠/和星号*:

在这里插入代码片
>>> def standard_arg(arg):
...     print(arg)
...
>>> def pos_only_arg(arg, /):
...     print(arg)
...
>>> def kwd_only_arg(*, arg): #second function and the third function,their arguments,one is only_positional,one is only_keyword.
...     print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
...     print(pos_only, srandard, kwd_inly)

第一个函数定义standard_arg,那是最熟悉的形式,在调用的方式上没有任何限制,参数借助位置或者关键字可以通过:

在这里插入代码片
>>>def standard_arg(2)
2
>>> standard_arg(arg=2)
2

第二个函数pos_only_arg就被限制,仅仅使用位置参数,在函数定义之中得有斜杠/才行。在这里才慢慢学会了一点markdown的书写方法,有一些代码还是要到编辑器上去打,如果想省事,直接在这个markdown上也是可以输入代码的。以下代码大部分都是在这个markdown上复制输入的。因为那个py编辑器,包括那个power shell转换的py编辑器,复制起来太让人紧张,稍不注意就全功今尽弃。

在这里插入代码片
>>>pos_only_arg(1)
1

>>>pos_only_arg(arg=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'

第三个函数kwd_only_arg(3),仅仅允许关键字参数才能通过,这个参数在函数定义中用一个星号*表示。

在这里插入代码片
>>> kwd_only_arg(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3

最后一种方式,就是在同样的函数定义中,使用所有这三种调用方式:

在这里插入代码片
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given

>>> combined_example(1, 2, kwd_only=3)
1 2 3

>>> combined_example(1, standard=2, kwd_only=3)
1 2 3

>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'

这里才是最后,思考这样一个函数定义,这个函数在位置参数名称和两个星号**关键字之间有些冲突,因为这个关键字参数也有如同键一样的一个名称:

在这里插入代码片
def foo(name, **kwds):
      return 'name' in kwds

当关键字的名称总是绑架在第一个参数上的时候,回车的结果成为真,这样的调用是不可能的。例如:

在这里插入代码片
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>

但是,使用斜杠符号/(仅仅位置参数),因为它允许名称作为位置参数,也允许名称作为关键字参数中的一个键的名称,所以,这样用还是可行的。

在这里插入代码片
def foo(name, /, **kwds):
    return 'name' in kwds
>>> foo(1, **{'name': 2})
True

换一句话表述,仅仅位置参数的名称可以用在没有歧义的关键字**kwds上。

标题4.7.3.5. 重述Recap

这个用例将判定哪一个参数去用在函数定义之中:

在这里插入代码片
>>> def f(pos1, pos2, /, pos_or_kwd1, kwd2):

如以下导引:
如果你想用到参数的名称,但它对用者又不大合适,那就使用仅仅位置参数。当函数被调用,或者如果你需要采用某些位置函数和任意关键字,你还想给参数的次序一个规定,同时这个参数的名称并没有什么实际意义,这种参数就是有用的。
当函数名称有意义,函数定义通过名称更容易理解,或者你想防止用户依赖被通过的参数位置,那就使用仅仅关键字参数。
对于一个API(Application Programming Interface,应用程序编程接口)而言,如果函数参数名在将来会被修改,使用仅仅位置参数将会防止中断API改变。

标题4.7.4. 任意参数列表Arbitrary Argument Lists

最后,用来选择的最小频率函数参数,那是指定一个带有任意参数数就可调用的函数。这些参数将会框在一个元组之内tuple(参看元组和序列)。在参数可变数的前面,零或者更多的标准参数可能出现。

在这里插入代码片
def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

通常,这些可变化的参数会一直放到正式的参数列表之中,因为它们提示所有剩余的输入参数,告知这些参数都在函数中通过。任何正式参数,这些参数出现在带星号*的参数之后,它们都是仅仅关键字参数,意思是,它们仅仅能够用作关键字而不是位置参数。

在这里插入代码片
>>> def concat(*args, sep="/"):
...     return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

标题4.7.5.解包参数列表 Unpacking Argument Lists

当参数已经在一个列表中或者元组中,但需要去对一个调用的函数解包的时候,这个调用又要求分隔开位置函数,这时候,反过来的情景就出现了。例如内置函数range()期待分离开启动和停止参数。如果它们不合宜地分隔开,写这个函数调用是用的星号*运算子去把一个列表或者元组以外的参数给解包了。

在这里插入代码片
>>> list(range(3, 6))            # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args))            # call with arguments unpacked from a list
[3, 4, 5]

在相同模式中,词典可以用双星号**运算子传递关键字参数:

在这里插入代码片
>>> def parrot(voltage, state='a stiff', action='voom'):
...     print("-- This parrot wouldn't", action, end=' ')
...     print("if you put", voltage, "volts through it.", end=' ')
...     print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !

标题4.7.6.拉姆达表达式 Lambda Expressions

小小的匿名函数可以用拉姆达关键字来创建。这个功能返回两个参数的和:lambda a, b: a+b。拉姆达函数可以用在函数对象所要求的无论什么地方。句法上限定为一个单一的表达式。在语义上,它们对于一个标准的函数定义恰像一颗句法上的糖。像嵌入函数定义一样,拉姆达函数可以从其含有的范围上指定变元。

在这里插入代码片
>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

以上函数例子使用了一个拉姆达表达式去返回一个函数。它的另一个用途是如同一个参数一样去通过一个小小的函数。

在这里插入代码片
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

标题4.7.7. 文件字符串Documentation Strings

这里是一些有关文本字符串的内容和格式化的常规
首行总是很短,简要概括对象目标的目的。为了做到简化,不是去清晰地陈述对象名称或者类型,因为这些东西使用其它手段也可以做到(除非名称碰巧是是一个描述函数操作的动词)。这行应该带大写字母开头,末端用句号。
如果文本字符串有多行,第二行应该是空白,可视地隔开首行概括与描述的其余部分。其后的行应该是描述对象调用常规和其附带影响等等,由此而构成的一个或者若干个段落。
python的分析器parser在python中,并不会不去从多行字符串文字缩进,这样文字建构过程中如果愿意缩进当然就必须缩进。使用以下常规可以做到这一点。字符串第一行以后的首行非空行判定对全部文件字符串缩进数。(我们不能使用第一行,因为第一行一般来说连接到字符串的开放引号,所以,它的缩进在字符串文字中是不明显的)等价于这个缩进的白色空间,接之分隔开了题头和这个字符串的开头。缩进少的那些行应该不出现,但是如果它们出现了,它们前面的白色空间就该消掉了。在制表符键tabs扩展之后,白色空间应该被测试。
这里是一个多行文本字符串的实例:

在这里插入代码片
>>> def my_function():
...     """Do nothing, but document it.
...
...     No, really, it doesn't do anything.
...     """
...     pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.

    No, really, it doesn't do anything.

标题4.7.8. 函数注释Function Annotations

函数注释完全是可选的元数据信息,这些信息是被用户定义的函数所使用的类型所相关的(更多的信息请参看PEP3107和PEP484):
注释被储存在函数__annotation__中,它如同一个词典表明函数的一些属性,但对函数本身毫无影响。参数注释被参数名之后的一个克隆colon所定义,后随一个表达式,这个表达式来对注释的值予以评注。返回的注释是用字符—>来定义的,也尾随一个表达式,该表达式处于参数列表和指示那个函数定义陈述的末端之间。以下实例有一个位置参数,一个关键字参数和返回值注释。

在这里插入代码片
>>> def f(ham: str, eggs: str = 'eggs') -> str:
...     print("Annotations:", f.__annotations__)
...     print("Arguments:", ham, eggs)
...     return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'

标题4.8.插曲:编码风格 Intermezzo: Coding Style

你要是准备写更长,更复杂的python片段,那就该谈谈编码的风格了。大多数语言都可以用不同风格来编写代码(或者更简明更格式化),有些语言比别的语言更易读。如果你想把用语言写的东西让别人理解起来读起来更容易,所写的编码含有一些好的观念,并且采用优雅的编码风格,那会给你的编码可理解性可读性以极大的帮助。
对python而言,PEP8已经产生大多数项目必得坚持的风格指南,它提供了一个非常具有可读性,还非常给人视觉愉悦的编码风格。每一个python开发者都应该去读读它,这里给出的也许是最能给你兴趣的几点。
第一,使用四格缩进,不用制表符键tabs。
四格是一个好的包容,在小缩进(允许更大一些嵌入深度)和大缩进(容易读)之间,这个四格刚好。制表符键tabs容易造成混淆,最好是拒绝用它。
第二,每行不要超过79个字符,包住行。
这帮助带有小显示规模的用户,并且使得合并成为可能,如果你有多个编码文件,就有可能把这些小文件一个一个的连串起来形成一个大的编码文件。
第三,使用空白行去分隔开函数和类,函数之中编码的较大模块也要使用这种方法。
第四,当可能的时候,把注释放在一个行中。
第五,使用文档字符串
第六,在运算符号和逗号之后使用空格,但是不要直接地在括号结构内部中使用:例如:a = f(1, 2) + g(3, 4).。
第七,一致性地命名你的类和函数,常规就是表示类的名称时,使用例如这样的命名方式UpperCameCase。表示函数和方法时,用下划线分开参数名,例如这样的方式:lowercase_with_underscores。
总是使用self作为表示第一个方法参数的名称(对于更多的类和方法的信息,请参看对于类的一瞥)
第八,如果你的编码意在国际环境中使用,不要去想象一些代码。python的缺省是UTF-8,或者甚至是更为平常的ASCII,它在任何情况下都能很好的发挥作用。
第九,另外,哪怕是有一点点机会论及不同的语言,用这种语言去阅读或者去做编码,在缩进时也不要使用非ASCII字符,

脚注
[1] 实际上被对象指称调用将是更好的一个描述,如果一个可变对象被通过,调用者将看到这个调用使得对象产生的任何变化(插进一个列表的项目)

猜你喜欢

转载自blog.csdn.net/weixin_41670255/article/details/108404935