【Python 笔记(3)】Python函数和函数式编程

详解 Python 函数和函数式编程

1. 什么是函数?

函数是对程序逻辑进行结构化或过程化的一种编程方法。
函数定义的方法:

def foo():
    print('bar')

1.1 函数 vs 过程

函数和过程两者都是可以被调用的实体。但是传统意义上的函数或者“黑盒”,可能不带任何输入参数,经过一定的处理,最后向调用者传回返回值。其中一些函数则是布尔类型的,返回一个“是”或者“否”的回答,更确切地说,一个非零或者零值。而过程是简单、特殊、没后返回的函数。
python 的过程就是函数,因为解释器会隐式地返回默认值 None。

1.2 返回值与函数类型

函数会向调用者返回一个值,而实际编程中大部分偏函数更接近过程,不显示地返回任何东西。在 python 中,无返回值的函数返回对象类型是 none。
下面 hello() 函数的行为就像一个过程,没有返回值。如果保存了返回值,该值为 None:

>>> def hello():
...     print('hello world')
...     
>>> res = hello()
hello world
>>> res
>>> print(res)
None
>>> type(res)
<class 'NoneType'>

另外,与其他大多数的语言一样,python 里的函数可以返回一个值或者对象。只是在返回一个容器对象的时候有点不同,看起来像是能返回多个对象。

def foo():
    return ["xyz", 1000000, -98.6]

def bar():
    return 'abc', [42, 'python'], "Guido"
>>> type(foo())
<class 'list'>
>>> type(bar())
<class 'tuple'>

foo() 函数返回一个列表,bar() 函数返回一个元祖。由于元祖语法上不需要一定带上圆括号,所以让人真的以为可以返回多个对象。如果我们要恰当地给这个元祖加上括号,bar() 的定义看起来会是这样:

def bar():
    return ('abc', [42, 'python'], "Guido")

从返回值的角度来考虑,可以通过很多方式来存储元祖。接下来的3种保存返回值得方式是等价的:

>>> aTuple = bar()
>>> x, y, z = bar()
>>> (a, b, c) = bar()
>>>
>>> aTuple
('abc', [42, 'python'], 'Guido')
>>> x, y, z
('abc', [42, 'python'], 'Guido')

在对 x、y、z 和 a、b、c 的赋值中,根据值返回的顺序,每个变量会接收到与之对应的返回值。而 aTuple 直接获得函数隐式返回的整个元祖。

简而言之,当没有显式地返回元素或者如果返回 None 时,python 会返回一个 None。如果函数返回一个对象,那么调用者接收的就是 python 返回的那个对象,且对象的类型仍然相同。如果函数返回多个对象,python把他们聚集起来并以一个元祖返回。是的,尽管我们声称 Python 比诸如 C 那样只允许返回一个返回值得语言灵活得多,但老实说,Python 也是遵循了相同的传统,只是让程序员误以为可以返回多个对象。

返回的对象的数目 Python 实际返回的对象
0 None
1 object
>1 tuple

2. 调用函数

2.1 函数操作符

同大多数语言相同,我们用一对圆括号调用函数。实际上,有些人认为(())是一个双字符操作符。正如你可能意识到的,任何输入的参数都必须放置在括号中。作为函数声明的一部分,括号也会用来定义那些参数。在 Python 中,参数的操作符同样用于类的实例化。

2.2关键字参数

关键字参数的概念仅仅针对函数的调用。这种理念是让调用者通过函数调用中的参数名字来区分参数。这样规范允许参数缺失或者不按顺序,因为解释器能通过给出的关键字来匹配参数的值。

def foo(x):
    print(x)    

标准调用

>>> foo(42)
42
>>> foo('bar')
bar
>>> y = 44
>>> foo(44)
44

关键字调用

>>> foo(x=42)
42
>>> foo(x='bar')
bar
>>> y = 44
>>> foo(x=y)
44

另外一个例子,net_conn()函数,需要两个参数 host 和 port:

def net_conn(host, port):
    print('connect to', str(host) + ':' + str(port))

只要按照函数声明中参数定义的顺序,输入恰当的参数,自然就可以调用这个函数:

>> net_conn('kappa', 8080)
connect to kappa:8080

host 参数得到字符串‘kappa’,port 参数得到整型 8080,当然也可以不按照函数声明中的参数顺序输入,如下:

>>> net_conn(port=8080, host='chino')
connect to chino:8080

当参数允许“缺失”的时候,也可以使用关键字参数。这取决于函数的默认参数。

2.3 默认参数

详见 5.2 小节。

2.4 参数组

Python 同样允许程序员执行一个没有显式定义参数的函数,相应的方法是通过一个把元祖(非关键字参数)或字典(关键字参数)作为参数组传递给函数。详见 6. 节。
基本上,你可以将所有参数放进一个元祖或者字典中,仅仅用这些装有参数的容器来调用一个函数,而不必显式地将它们放在函数调用中:

func(*tuple_grp_nonkw_args, **dict_grp_ke_args)

其中的 tuple_grp_nonkw_args 是以元祖形式体现的非关键字参数组,dict_grp_kw_args 是装有关键字参数的字典。python 允许我们把变量放在元祖或字典里,并在没有显式地对参数进行逐个声明的情况下,调用函数。
实际上,我们也可以给出形参!这些参数包括标准的位置参数和关键字参数,所以在 Python 中允许的函数调用的完整语法为:

func(positional_grgs, keyword_args, *tuple_grp_nonkw_args, **dict_grp_ke_args)

例子 easyMath.py 是一个儿童算数游戏,可以随机选择算数加减法。我们通过函数 add(),sub()等价“+”“-”操作符,这两者都可以在 operator 模块中找到。接着我们生成一个参数列表(该列表只有2个参数,因为这些是二元操作符/运算)。接着选择任意的数字作为算子。因为我们没打算在这个程序的基础版本中支持负数,所以我们将两个数字的列表按从大到小的顺序排序,然后用这个参数列表和随机选择的算术操作符去调用相应的函数,最后获得问题的正确答案。
随机选择数字以及一个算术函数,显式问题,以及验证结果。在3次错误的尝试以后给出结果,等到用户输入一个正确的答案后便会继续运行。

easyMath.py

#!/usr/bin/env python

from operator import add, sub
from random import randint, choice

ops = {'+': add, '-': sub}
MAXTRIES = 2

def doprob():
    op = choice('+-')
    nums = [randint(1, 10) for i in range(2)]
    nums.sort(reverse=True)
    ans = ops[op](*nums)         # ans = add(*nums) 或者 ans = sub(*nums) ,元祖非关键字参数组   
    pr = '%d %s %d=' %  (nums[0], op, nums[1])
    oops = 0
    while True:
        try:
            if int(input(pr)) == ans:
                print('correct')
                break
            if oops == MAXTRIES:
                print('answer\n%s%d' % (pr, ans))
            else:
                print('incorrect... try again')
                oops += 1
        except (KeyboardInterrupt, EOFError, ValueError):
            print('invalid input... try again')

def main():
    while True:
        doprob()
        try:
            opt = input('Again? [y]').lower()
            if opt and opt[0] == 'n':
                break
        except (KeyboardInterrupt, EOFError):
            break

if __name__ == '__main__':
    main()

运行结果:

1 + 1=>? 2
correct
Again? [y]>? y
7 + 4=>? 11
correct
Again? [y]>? y
8 + 8=>? 16
correct
Again? [y]>? y
7 + 5=>? 9
incorrect... try again
7 + 5=>? 9
incorrect... try again
7 + 5=>? 9
answer
7 + 5=12
7 + 5=>? 19
answer
7 + 5=12
7 + 5=>? 12
correct
Again? [y]>? y
8 - 4=>? 4
correct
Again? [y]>? n

3. 创建函数

3.1 def 语句

函数是用 def 语句来创建的,语法如下:

def function_name(arguments):
    "function_documentation_string"
    function_body_suite

标题行由 def 关键字,函数的名字,以及参数的集合(如果有的话)组成。def 子句的剩余部分包括了一个虽然可选但是强烈推荐的文档子串和必须的函数体。

def function_name(who):
    "returns a salutory string customized with the input"
    return "Hello" + str(who)

3.2 声明与定义比较

在某些编程语言里,函数声明和函数定义区分开的。一个函数声明包括提供对函数名,参数的名字(传统上还有参数的类型),但不必给出函数的任何代码,具体的代码通常属于函数定义的范畴。
在声明和定义有区别的语言中,往往是因为函数的定义可能和其声明放在不同的文件中。Python 将这两者视为一体,函数的子句由声明的标题行以及随后的定义体组成。

3.3 前向引用

和其他高级语言类似,Python 也不允许在函数未声明之前,对其进行引用或者调用。我们下面给出几个例子看一下:

>>> def foo():
...     print('in foo()')
...     bar()
...     

如果我们调用函数 foo(),肯定会失败,因为函数 bar() 还没有声明:

>>> foo()
in foo()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 3, in foo
NameError: name 'bar' is not defined

我们现在定义函数 bar(),在函数 foo() 前给出 bar() 的声明:

>>> def bar():
...     print("in bar()")
...     
>>> def foo():
...     print("in foo()")
...     bar()
...     

现在我们可以安全的调用 foo(),而不会出现任何问题:

>>> foo()
in foo()
in bar()

事实上,我们甚至可以在函数 bar() 前定义函数 foo():

>>> def foo():
...     print('in foo()')
...     bar()
...     
>>> def bar():
...     print('in bar()')
...     

太神奇了,这段代码可以非常好的运行,不会有前向引用的问题:

>>> foo()
in foo()
in bar()

这段代码是正确的,因为即使(在 foo()中)对 bar() 进行的调用出现在 bar() 的定义之前,但 foo() 本身不是在 bar() 声明之前被调用的。换句话说,我们声明 foo(),然后再声明 bar(),接着调用 foo(),但是到那时,bar() 已经存在了,所以调用成功。
注意 foo() 在没有错误的情况下成功输出了 foo()。名字错误是当访问没有初始化的标识符时才产生的异常。

3.4 函数属性

你可以获得每个 Python 模块、类和函数中任意的名称空间。你可以在模块 foo 和 bar 里都有名为 x 的一个变量,但是在将这两个模块导入你的程序后,仍然可以使用这两个变量。所以,即使在两个模块中使用了相同的变量名字,这也是安全的,因为句点属性标识对于两个模块意味了不同的命名空间,比如说,在这段代码中没有名字冲突:

import foo, bar

print(foo.x + bar.x)

函数属性是 Python 另外一个使用了句点属性标识并拥有名称空间的领域。

def foo():
    'foo() -- properly created doc string'

def bar():
    pass

bar.__doc__ = 'Oops, forgot the doc str above'
bar.version = 0.1

上面的 foo() 中,我们以常规的方式创建了我们的文档子串,比如,在函数声明后第一个没有复制的字串。当声明 bar() 时,我们什么都没做,仅用了句点属性标识类增加文档字串以及其他属性。我们可以接着任意地访问属性。下面是一个使用了交互解释器的例子(你可能已经发现,用内建函数 help() 显式会比用 __doc__ 属性更漂亮,但是你可以选择你喜欢的方式)。

>>> help(foo)
Help on function foo in module __main__:

foo()
    foo() -- properly created doc string

>>> print(bar.version)
0.1
>>> print(foo.__doc__)
foo() -- properly created doc string
>>> print(bar.__doc__)
Oops, forgot the doc str above

注意我们是如何在函数声明外定义一个文档字串。然而我们仍然可以就像平常一样,在运行时刻访问它。然而你不能在函数的声明中访问属性。换句话说,在函数声明中没有 “self” 这样的东西让你可以进行诸如 __dict__[‘version’]=0.1的赋值。这是因为函数体还没有被创建,但之后你有了函数对象,就可以按我们在上面描述的那样方法来访问它的字典。另外一个自由的名称空间!
函数属性实在 2.1 中添加到Python 中的,你可以在 PEP232 中阅读到更多相关信息。

3.5 内部/内嵌函数

在函数体内创建另外一个函数(对象)是完全合法的。这种函数叫做内部/内嵌函数。因为现在 Python 支持静态地嵌套域(在2.1中引入但是到2.2时才时标准),内部函数实际上很有用的。内嵌函数对于较老的 Python 版本没有什么意义,那些版本中只支持全局和一个局部域。那么如何去创造一个内嵌函数呢?
最明显的创造内部函数的方法是在外部函数的定义体内定义函数(用 def 关键字),如:

def foo():
    def bar():
        print('bar() called')
    print('foo() called')
    bar()

foo()
bar()

我们将以上代码置入一个模块中,如 inner.py,然后运行,会得到如下输出:

foo() called
bar() called
Traceback (most recent call last):
  File "inner.py", line 8, in <module>
    bar()
NameError: name 'bar' is not defined

内部函数一个有趣的方面在于整个函数体都在外部函数的作用域(既是你可以访问一个对象的区域)之内。如果没有任何对 bar() 的外部引用,那么除了在函数体内,任何地方都不能对其进行调用,这就是在上述代码执行到最后看到异常的原因。
另外一个函数体内创建函数对象的方式是使用 lambda 语句。详见 7.1 小节。
如果内部函数的定义包含了在外部函数里定义的对象的引用(这个对象甚至可以是在外部函数之外),内部函数会变成被称为闭包(closure)的特别之物。详见 8.4 小节。

3.6 函数(与方法)装饰器

装饰器背后的主要动机源自Python面向对象编程。装饰器是在函数调用之上的装饰。这些装饰仅是当声明一个函数或者方法的时候,才会应用的额外调用。
装饰器的语法以@开头,接着是装饰器函数的名字和可选的参数。紧跟着装饰器声明的是被修饰的函数和装饰函数的可选参数。装饰器看起来会是这样:

@decorator(dec_opt_arga)
def func2Bdecorated(func_opt_args):
   ...
   ...

下面介绍装饰器背后的灵感!
当静态方法和类方法在2.2时被加入到Python中的时候,实现方法很笨拙:

class MyClass(object):
    def staticFoo():
    ...
    ...
    staticFoo = staticmethod(staticFoo)

(澄清一下,在那个发行版本,这不是最终地语法)
在这个类的声明中,我们定义了叫staticFoo()的方法。现在因为打算让它成为静态方法,省去了 self 参数。接着用staticmethod()内建函数来将这个函数转化为静态方法,但是在def staticFoo()后跟着staticFoo = staticmethod(staticFoo)显得多么的臃肿。使用装饰器可以用如下代码替换掉上面的:

class MyClass(object):
   @staticmethod
   def staticFoo():
   ...
   ...
 

此外,装饰器可以如函数一样“堆叠”起来,这里有一个更加普遍的例子,使用了多个装饰器:

@deco2
@deco1
def func(arg1, arg2, ...);
    pass

这和创建一个组合函数是等价的。

def func(arg1, arg2, ...):
    pass

func = deco2(deco1(func))

函数组合用数学来定义就像这样: ( g f ) ( x ) = g ( f ( x ) ) (g \cdot f)(x) =g(f(x)) 。对于在Python中的一致性:

@g
@f
def foo():
    ...
    ...
    

foo = g(f(foo))

相同

  1. 有参数和无参数的装饰器
    装饰器语法让人困扰的地方在于,什么时候使用带参数或不带参数的装饰器。

没有参数的情况:

@deco
def foo();
    pass

非常地直接

foo = deco(foo)

跟着是无参数函数(如上面所见)组成。

然而,带参数的装饰器 decomaker():

@decomaker(deco_args)
def foo():
    pass

需要自己返回以函数作为参数的装饰器。换句话说,decomaker()deco_args 做了些事并返回函数对象,而该函数对象正是以 foo 作为其参数的装饰器。简单地说:

foo = decomaker(deco_args)(foo)

列出一个含有多个装饰器的例子,其中的一个装饰器带有一个参数:

@deco1(deco_arg)
@deco2
def func():
    pass

这等价于:

func = deco1(deco_arg)(deco2(func))
  1. 什么是装饰器
    现在我们知道装饰器实际就是函数。我们也知道它们接受函数对象
    但它们是怎么样处理那些函数的呢?

  2. 装饰器举例
    下面给出一个例子。这个例子通过显示函数执行的时间“装饰”了一个(没有用的)函数。这是一个“时戳装饰”。

这个装饰器(以及闭包)示范表明装饰器仅仅是用来“装饰”函数的包装,返回一个修改后的函数对象,将其重新赋值原来的标识符,并永久失去对原始函数对象的访问。

from time import ctime, sleep


def tsfunc(func):
    def wrappedFunc():
        print('[%s] %s() called' % (ctime(), func.__name__))
        return func()
    return wrappedFunc


@tsfunc
def foo():
    pass


foo()
sleep(4)

for i in range(2):
    sleep(1)
    foo()

运行脚本,得到输出结果;

[Wed Jan 16 16:59:17 2019] foo() called
[Wed Jan 16 16:59:22 2019] foo() called
[Wed Jan 16 16:59:23 2019] foo() called

逐行解释

  • 5 ~ 10行
    在启动和模块导入代码之后,tsfunc()函数式一个显示何时调用函数的时戳的装饰器。它定义了一个内部的函数wrappedFunc(),该函数增加了时戳以及调用了目标函数。装饰器的返回值是一个包装了的函数。
  • 12 ~ 21行
    用空函数题(什么都不做)来定义了foo()函数并用tsfunc()来装饰。为证明我们的设想,立刻调用它,然后等待4秒,然后在调用两次,并在每次调用前暂停1每秒。
  1. 无参数装饰器对比试验
print('------ flag: 0 ------\n')


def decofunc1(func):
    """
    :param func: a function object
    :return: a function object
    """
    print('%s() called' % func.__name__)
    return func


print('\n------ flag: 1 ------\n')


def decofunc2(func):
    """
    :param func: a function object
    :return: a function object
    """
    def wrappedFunc():
        print('%s() called' % func.__name__)
        return func()
    return wrappedFunc


print('\n------ flag: 2 ------\n')


@decofunc1
def foo1():
    print('foo1')
    pass


print('\n------ flag: 3 ------\n')


@decofunc2
def foo2():
    print('foo2')
    pass


print('\n------ flag: 4 ------\n')

foo1()

print('\n------ flag: 5 ------\n')

foo2()

print('\n------ flag: 6 ------\n')

foo1_ = decofunc1(foo1)

print('\n------ flag: 7 ------\n')

foo2_ = decofunc2(foo2)

print('\n------ flag: 8 ------\n')

foo1_()

print('\n------ flag: 9 ------\n')

foo2_()

print('\n------ flag:10 ------\n')

print('foo1 = foo1_?: %s' % str(foo1 == foo1_))
print('foo2 = foo2_?: %s' % str(foo2 == foo2_))

print('\n------ flag:11 ------\n')

输出结果如下:

------ flag: 0 ------


------ flag: 1 ------


------ flag: 2 ------

foo1() called

------ flag: 3 ------


------ flag: 4 ------

foo1

------ flag: 5 ------

foo2() called
foo2

------ flag: 6 ------

foo1() called

------ flag: 7 ------


------ flag: 8 ------

foo1

------ flag: 9 ------

wrappedFunc() called
foo2() called
foo2

------ flag:10 ------

foo1 = foo1_?: True
foo2 = foo2_?: False

------ flag:11 ------

4. 传递函数

在 Python 中可以用其他的变量来作为函数的别名。
因为所有的对象都是通过引用来传递的,函数也不例外。当对一个变量赋值时,实际是将相同对象的引用赋值给这个变量。如果对象时函数的话,这个对象所有的别名都是可调用的。

>>> def foo():
...     print('in foo()')
...     
>>> bar = foo
>>> bar()
in foo()

当我们把 foo 赋值给 bar 时,bar 和 foo 引用了同一个函数对象,所以能以和调用 foo() 相同的方式来调用 bar()。区分 “foo”(函数对象的引用)和 “foo()”(函数对象的调用)的区别。
我们甚至恶意把函数作为参数传入其他函数来进行调用。

>>> def bar(argfunc):
...     argfunc()
...     
>>> bar(foo)
in foo()

注意到函数对象 foo 被传入到 bar() 中。 bar() 调用了 foo() (用局部变量 argfunc 来作为其别名就如同在前面的例子中我们把 foo 赋给 bar 一样)。

研究 numConv.py。一个函数作为参数传递,并在函数体内调用这些函数。这个脚本用传入的转换函数简单将一个序列的数转化为相同的类型。特别地,test() 函数传入一个内建函数 int() 或 float()来执行转换。

numConv.py

#!/usr/bin/env python

def convert(func, seq):
    """conv. sequence of numbers to same type"""
    return [func(eachNum) for eachNum in seq]

myseq = (123, 45.67, -6.2e8, 999999999)
print(convert(int, myseq))
print(convert(float, myseq))
[123, 45, -620000000, 999999999]
[123.0, 45.67, -620000000.0, 999999999.0]

5. Formal Arguments

Python 函数的形参集合由在调用时传入函数的所有参数组成,这参数与函数声明中的参数列表精确地配对。这些参数包括了所有必要参数(以正确的定位顺序来传入函数的)、关键字参数(以顺序或者不按顺序传入,但是带有参数列表中曾定义过的关键字)和所有含有默认值,函数调用时不必要指定的参数。(声明函数时创建)局部命名空间为各个参数值,创建了一个名字。一旦函数开始执行,就能访问这个名字。

5.1 位置参数

位置参数必须以在被调用函数体中定义的准确顺序来传递。另外,没有任何默认参数的话,传入函数(调用)的参数的精确地数目必须和声明的数字一致。

>>> def foo(who):
...     print('Hello', who)
...     
>>> foo()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: foo() missing 1 required positional argument: 'who'
>>> 
>>> foo('World!')
Hello World!
>>> 
>>> foo('Mr.', 'World!')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: foo() takes 1 positional argument but 2 were given

foo() 函数由一个位置参数。这意味着任何对 foo() 的调用必须有唯一的一个参数,不多,不少。否则你会频频看到 TypeError。Python 的错误具有强的信息性。作为一个普遍的规则,无论何时调用函数,都必须提供函数的所有位置参数。可以不按位置地将关键字参数传入函数,给出关键字来匹配其在参数列表中的何时的位置是被允许的。
由于默认参数的特性,他们是函数调用的可选部分。

5.2 默认参数

对于默认参数如果在函数调用时没有为参数提供值则使用预先定义的默认值。这些定义在函数声明的标题行中给出。C++ 也支持默认参数,和 Python 有同样的语法:参数名等号默认值。这个从语法上来表明如果没有值传递给那个参数,那么这个参数将取默认值。

Python 中默认值声明变量的语法是所有的位置参数必须出现在任何一个默认参数之前。

def func(posargs, defarg1=dval1, defarg2=dval2, ...):
    """function_documentation_string"""
    function_body_suite

每个默认参数都紧跟着一个用默认值得赋值语句。如果在函数调用时没有给出值,那么这个赋值就会实现。

1. 为什么用默认参数
默认参数让程序的健壮性上升到极高的程度,因为它们补充了标准位置参数没有提供的一些灵活性。这种简洁极大的帮助了程序员。当少几个需要操心的参数时候,生活不再那么复杂。这在一个程序员刚接触到一个 API 接口时,没有足够的知识来给参数提供更对口的值时显得尤为有帮助。
使用默认参数的概念与在你的电脑上安装软件的过程类似。一个人会有多少次选择默认安装而不是自定义安装?我可以说可能几乎都是默认安装。这既方便、易于操作,又能节省时间。总是选择自定义安装的只是少数人。
另外一个让开发者受益的地方在于,开发者能更好地控制为顾客开发的软件。当提供了默认值得时候,他们可以精心选择“最佳”的默认值,所以用户不需要马上面对繁琐的选项。随着时间流逝,当用户对系统或者API越来越熟悉的时候,他们最终能自行给出参数。

举例:

>>> def taxMe(cost, rate=0.0825):
...     return cost + (cost * rate)
... 
>>> taxMe(100)
108.25
>>> taxMe(100, 0.05)
105.0

所有必需的参数都要在默认参数之前。因为它们是强制性的,但默认参数不是。从语法构成上看,对于解释器来说,如果允许混合模式,确定什么值来匹配什么参数是不可能的。如果没有按正确的顺序给出参数,将会报语法错误。

>>> def taxMe2(rate=0.0825, cost):
...     return cost * (1.0 + rate)
...
  File "<stdin>", line 1
SyntaxError: non-default argument follows default argument

再来看看 net_conn()。

def net_conn(host, port):
    net_conn_suite

读者应该还记得,如果命名了参数,这里可以不按顺序给出参数。由于有了上述声明,我们可以做出如下(规则的)位置或者关键字参数调用:

net_conn('kappa', 8000)
net_conn(port=8080, host='chino')

然而,如果我们将默认参数引入这个等式,情况就会不同,虽然上面的调用仍然有效。让我们修改下 net_conn() 的声明以使端口参数有默认值80,在增加另外的名为 stype(服务器类型)默认值为 ‘tcp’ 的参数:

def net_conn(host, port=80, stype='tcp'):
    net_conn_suite

我们已经扩展了调用 net_conn()的方式。以下就是所有对 net_conn()有效的调用:

net_conn('phaze', 8000, 'udp')           # no def args used
net_conn('kappa')                        # both def args used
net_conn('chino', stype='icmp')          # use port def arg
net_conn(stype='udp', host='solo')       # use port def arg
net_conn('deli', 8080)                   # use stype def arg
net_conn(port=81, host='chino')          # use stype def arg

在上面所有的例子中,一直不变的是:需要提供唯一的必须参数,host。host 没有默认值,所以他必须出现在所有对 net_conn()的调用中。关键字参数已经被证实能给不按顺序的位置参数提供参数,结合默认参数,它们同样也能被用于跳过缺失的参数。

2. 默认函数对象参数举例
grapWeb.py 脚本主要目的是从互联网上抓取一个 Web 页面并暂时储存到一个本地文件中用于分析的简单脚本。


6. 可变长度的参数

7. 函数式编程

8. 变量作用域

9. 递归

10. 生成器

猜你喜欢

转载自blog.csdn.net/RadiantJeral/article/details/86503272