4.7. 深入 Python 函数定义

在Python中,你也可以定义包含若干参数的函数。这里由三种可用的形式,也可以混合使用。

4.7.1.默认参数值

最常用的一种形式是为一个或多个参数指定默认值。这会创建一个可以使用比定义时定义的参数更少的参数调用的函数,例如:

def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
    while True:
        ok = input(prompt)
        if ok in ('y' , 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop' ,'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise OSError('uncooperative user')
        print(complaint)

这个函数可以通过集中不同的方式调用:
- 只给出必要的参数:
ask_ok('Do you really want to quit?')
- 给出一个可选的参数:
aks_ok('Ok to overwrite the file?', 2)
- 或者给出所有的参数:
aks_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

重要警告:默认值只被赋值一次。这使得当默认值是可变对象时会有所不同,比如列表、字典或者大多数类的实例。例如,下面的函数在后续调用过程中会累计(前面)传给它的参数:

def f(a, L=[]):
    L.append(a)
    return L
print(f(1))
print(f(2))
print(f(3))
[1]
[1, 2]
[1, 2, 3]

如果你不想让默认值在后续调用中累积, 你可以像下面一样定义函数:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

4.7.2.关键字参数

函数可以通过关键字参数 的形式来调用,形如keyword = value。例如, 以下的函数:

def parrot(voltage, state='a stiff', action='voom', type='Norwegian 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)。可以用以下的任一方法调用:

parrot(1000)       # 1 位置参数
parrot(voltage=1000)  #1关键字参数
parrot(voltage=1000000, action='VOOOOOM') #2关键字参数
parrot(action = 'VOOOOOM', voltage=100000) #2关键字参数
parrot('a millon', 'bereft of life', 'jump')#3位置参数
parrot('a thousand', state='pushing up the daisies')#1位置参数1关键字参数
-- This parrot wouldn't voom if you put 1000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't voom if you put 1000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't VOOOOOM if you put 1000000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't VOOOOOM if you put 100000 volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's a stiff !
-- This parrot wouldn't jump if you put a millon volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's bereft of life !
-- This parrot wouldn't voom if you put a thousand volts through it.
-- Lovely plumage, the Norwegian Blue
-- It's pushing up the daisies !

不过以下集中调用是无效的:

parrot() #缺少必选参数
parrot(voltage=5.0, 'dead') #没有指定关键字
parrot(110, voltage=220) #两个参数都指向voltage这同一个对象
parrot(actor = 'John Cleese') #未命名过的关键字参数
  File "<ipython-input-8-0ff7c827c375>", line 2
    parrot(voltage=5.0, 'dead') #没有指定关键字
                       ^
SyntaxError: positional argument follows keyword argument

在函数调用中,关键字的参数必须跟随在位置参数的后面。传递的所有关键字参数必须与函数接受的某个参数相匹配(例如acter不是parrot函数的有效参数),它们的顺序并不重要。这也包括非可选参数(例如parrot(voltage=1000)也是有效的)。任何参数都不可以多次赋值。下面的示例由于这种限制将失败:

def function(a):
    pass
function(0, a=0)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-1-c27f71aecf56> in <module>()
      1 def function(a):
      2     pass
----> 3 function(0, a=0)


TypeError: function() got multiple values for argument 'a'

引入一个形如**name的参数时,它接收一个字典,该字典包含了所有未出现在形式参数列表中的关键字参数。这里可能还会组合使用一个形如*name的形式参数,它接收一个元组,包含了所有没有出现在形式参数列表中的参数值(*name必须在**name之前出现)。例如,我们这样定义一个函数:

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)
    keys = sorted(keywords.keys())
    print(keys,'排序后的') 
    print(keywords.keys(),'未排序的')
    for kw in keys:
        print(kw, ":",keywords[kw])

它可以像这样调用:

cheeseshop("Limburger", "It's very runny, sir.",
          "It's really very, Very runny, sir.",
          shopkeeper="Michael Palin",
          client="John Cleese",
          sketch="Cheese Shop Sketch")
-- Do you have any Limburger ?
-- I'm sorry,we're all out of Limburger
It's very runny, sir.
It's really very, Very runny, sir.
----------------------------------------
['client', 'shopkeeper', 'sketch'] 排序后的
dict_keys(['shopkeeper', 'client', 'sketch']) 未排序的
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch

注意在打印关键字参数之前,通过对关键字字典keys()方法的结果进行排序,生成了关键字参数的列表;如果不这样做,打印出来的参数的顺序是未定义的。(这里我记得是因为字典本身不提供排序,所以用字典的时候要注意排序)

4.7.3 可变参数列表

最后,一个最不常用的选择是可以让函数调用可变个数的参数。这些参数被包装进一个元组。在这些可变个数的参数之前,可以有0到多个普通的参数:

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

通常,这些可变参数是参数列表中的最后一个,因为它们将把所有的剩余输入参数传递给函数。任何出现在*args后的参数是关键字参数,这意味着,它们只能被用作关键字,而不是位置参数:

def concat(*args, sep="/"):
    return sep.join(args) #sep作为分隔符,将输入的单词连起来,默认是用/
concat('earth', 'mars', 'venus')
'earth/mars/venus'
concat('earth', 'mars', 'venus', sep='.')#除非用关键字参数的方法定义sep,否则接收不到
'earth.mars.venus'

4.7.4 参数列表的分拆

另有一种相反的情况:当你要传递的参数已经是一个列表, 但要调用的函数却接受分开一个个的参数值。这时候你要把已有的列表拆开来。例如内建函数range()需要独立的start,stop参数。你可以在调用函数时加一个*操作符来自动把列表拆开:

list(range(3, 6))
[3, 4, 5]
args = [3, 6]
list(range(*args))
[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 millon","state":"bleedin' demised","action":"VOOM"}
parrot(**d)
-- This parrot wouldn't VOOM if you put four millon volts through it. E's bleedin' demised !
#自己写个函数练习一下可变参数列表,在定义时使用*和**
def enemy(name, HP,*othername,**other_features):
    print("This enemy's name is",name)
    print("It's HP is",HP)
    print("It also has those names:",end=" ")
    for i in othername:
        print (i,end=" ")
    else:
        print()
    print("And it has those features:")
    keys = sorted(other_features.keys())
    for key in keys:
        print(key, ':', other_features[key])
enemy("Ice", 100, "Frost", "Frozen", fire_afraid = True, powerful = True)
This enemy's name is Ice
It's HP is 100
It also has those names: Frost Frozen 
And it has those features:
fire_afraid : True
powerful : True
#再写个函数练习一下分拆参数列表,在调用时使用*和**,这个例子证明了在调用函数时也可以同时使用*和**.
def enemy2(name,HP,othername,feature):
    print("This enemy's name is", name)
    print("It's HP is", HP)
    print("It also has this names:", othername)
    print("And it has this feature:", feature)
name_HP_list = ["Ice",100]
other_dic = {"othername":"Frost","feature":"fire_afraid"}
enemy2(*name_HP_list,**other_dic)
This enemy's name is Ice
It's HP is 100
It also has this names: Frost
And it has this feature: fire_afraid

4.7.5.Lambda形式

出于实际需要,有几种通常在函数式编程语言例如Lisp中出现的功能加到了Python。通过lambda关键字,可以创建短小的匿名函数。下面这个函数可以返回它的两个参数的和:lambda a, b:a+b 。Lambda形式可以用于任何需要的函数对象。出于语法限制,它们只能有一个单独的表达式。语义上讲,它们只是普通函数定义中的一个语法技巧。类似于嵌套函数定义,lambda形式可以从外部作用域引用变量:

def make_incrementor(n):
    return lambda x: x + n #lambda后面接的是参数,冒号后是return的值
f = make_incrementor(42)
f(0)
42
f(1)
43

上面的示例是使用lambda表达式返回一个函数。另一个用途是将一个小函数作为参数传递:

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.6 文档字符串(函数的注释)

这里介绍的是文档字符串的概念和格式。

第一行的内容应该是关于这个函数的用途的简介。简短起见,不用明确的陈述对象名或类型,因为它们可以从别的途径了解到,这一行应该以大写字母开头,以句号结尾。

如果文档字符串有多行,第二行应该空出,与接下来的详细描述明确分隔。接下来的文档应该有一或多段描述对象的调用约定、边界效应等。

Python 的解释器不会从多行的文档字符串中去除缩进,所以必要的时候应当自己清除缩进。这符合通常的习惯。第一行之后的第一个非空行决定了整个文档的缩进格式。(我们不用第一行是因为它通常紧靠着起始的引号,缩进格式显示的不清楚。)留白“相当于”是字符串的起始缩进。每一行都不应该有缩进,如果有缩进的话,所有的留白都应该清除掉。留白的长度应当等于扩展制表符的宽度(通常是8个空格)。

下面是一个多行文档字符串的示例:

def my_function():
    '''Do nothing,but document it.

    No, really,it doesn't do anything.
    '''
    pass
print(my_function.__doc__) #函数.__doc__返回的是文档字符串。
Do nothing,but document it.

    No, really,it doesn't do anything.

4.7.7.函数注解

函数注解是关于用户自定义函数的完全可选的,随意的元数据信息。无论Python本身或者标准库中都没有使用函数注解;本节只是描述了语法。第三方的项目是自由地为文档,类型检查,以及其他用途选择函数注解。

注解是以字典形式存储在函数的__annotations__属性中,对函数的其他部分没有任何影响。参数注解(Paramenter annotations)是定义在参数名称的冒号后面, 紧随着一个用来表示注解的值的表达式。 返回注释(Return annotations) 是定义在一个->后面,紧随着一个表达式,在冒号与->之间。 下面的示例包含一个位置参数,一个关键字参数,和没有意义的返回值注释:

def f(ham: 123123,eggs: int = 'spam') ->"Nothing to see here":
#参数冒号后面紧随的就是参数注解,可以随意写,就算把eggs注释成int也没关系
#->后跟的就是return的注解,注解了return的是什么
    print("Annotations:", f.__annotations__)
    print("Arguments:" ,ham, eggs)
f('wonderful')
Annotations: {'ham': 123123, 'eggs': <class 'int'>, 'return': 'Nothing to see here'}
Arguments: wonderful spam

4.8. 插曲:编码风格

  • 使用4空格缩进,而非TAB,在可以嵌套更深的小缩进于更易读懂的大缩进之间,4空格是一个很好的这种。TAB会引发一些混乱,最好弃用。(去百度了一下,不用TAB是因为每个编辑器对TAB的定义不用,有的相隔4个有的相隔8个)
  • 确保一行不会超过79个字符,这有助于小显示器用户阅读,也能让大显示器并排显示几个代码文件,
  • 使用空行分隔函数和类,以及函数中的大块代码。
  • 可能的话,注释独占一行
  • 使用文档字符串
  • 把空格放到操作符两边,以及逗号后面,但是括号里侧不加空格:a = f(1, 2) + g(3, 4)
  • 统一函数和类命名,推荐类名用驼峰命名,函数和方法名用小写_和下划线。总是用self作为方法的第一个参数。
  • 如果你的代码是要在国际化环境运行,就不要使用花哨的编码。Python默认情况下是用UTF-8。甚至普通的ASCII也是很好的。
  • 同样,也不要使用非ASCII字符的标识符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值