Python语法速查: 7. 函数基础

 

 (1)函数基本

● 函数是第一类对象python

Python中万物皆对象,全部对象都是第一类的(first class),函数也不例外,也是第一类对象。既然是对象,那就能够看成普通的对象数据处理,好比:拥有本身的属性、可赋值给一个变量、可放入容器存储、可做为另外一个函数的参数或是返回值等等。当你在使用 def 定义函数时,就至关于生成了一个函数对象。闭包

下例中,将3个内置函数放入一个序列,并用迭代器取出后分别调用:并发

line = 'abc,1,3.14'
fun_list = [str, int, float]
para_list = line.split(',')
obj_list = [f(val) for f, val n zip(fun_list, para_list)]

# obj_list结果为:['abc', 1, 3.14]

 

● 文档字符串app

一般,将函数def后的第一行,做为描述函数用途的“文档字符串”,保存在函数的__doc__属性中。用内置函数 help(函数名) 也能够查看函数的描述文档。函数

 

● 可调用类型spa

可调用类型表示支持函数调用操做的对象,包括:用户定义的函数、内置函数、实例方法、类、提供了可调用接口的实例。可使用内置函数 callable() 来检查一个对象是不是可调用的。线程

类都是是能够调用的,调用类时,会自动将入参传递给类的__init__()方法,用以建立一个新实例。code

实例对象通常是不可调用的,可是若是这个实例实现了__call__()方法,那么这个实例就可直接调用。例如:若x是某个实例,执行 x(args) 就至关于调用 x.__call__(args) 方法。对象

 

● 函数的属性blog

函数做为一种对象,理论上能够给函数添加任意属性。函数也有一些内部默认的属性,见下列各表。

“内置函数”具备如下属性

属性 描述
__doc__ 文档字符串。
__name__ 函数名称。
__self__ 与方法相关的实例。 说明:对于像len()这样的内置函数,__self__为None(代表未绑定);而像s.append()这样的内置方法,__self__为列表对象s。

 

“用户定义函数”具备如下属性

属性 描述
__doc__ 文档字符串。
__name__ 函数名称。
__dict__ 包含函数属性的字典。
__code__ 编译后的代码。
__defaults__ 包含默认参数的元组。
__globals__ 函数应用时对应的全局命名空间的字典。
__clousre__ 闭包(包含与嵌套做用域相关数据的元组)

 

“实例方法”具备如下属性

属性 描述
__doc__ 文档字符串。
__name__ 方法名称。
__class__ 定义该方法的类。
__func__ 实现方法的函数对象
__self__ 与方法相关的实例(若是是非绑定方法,则为None)

 

 

● 绑定与非绑定方法

经过实例调用方法时,有绑定和非绑定两种用法。绑定方法封装了成员函数和一个对应实例,调用绑定方法时,实例会做为第一个参数self自动传递给方法。而非绑定方法仅封装了成员函数,并无实例,用户在调用非绑定方法时,须要显式地将实例做为第一个参数传递进去。详见下面2个例子

绑定用法(bound method):

class Foo():
    def meth(self, a):
        print(a)

obj = Foo()     # 建立一个实例
m = obj.meth    # 将meth方法绑定到obj这个实例上
m(2)            # 调用这个方法时,Python会自动将obj做为self参数传递给meth()方法

非绑定用法(unbound method):

class Foo():
    def meth(self, a):
        print(a)

obj = Foo()     # 建立一个实例
um = Foo.meth   # 非绑定,仅仅是将这个方法赋值给um,并不须要实例存在
um(obj, 2)      # 调用这个非绑定方法时,用户须要显式地传入obj实例做为第一个参数。

 

 

● 匿名函数

使用 lambda 语句能够建立表达式形式的匿名函数,其用途是指定短小的回调函数。语法为:

lambda 参数 : 表达式

lambda匿名函数中不能出现非表达式语句、也不能出现多行语句。

下例定义一个匿名函数:

a = lambda x,y: x*y
b = a(2,5)   # b的结果为:10

下例在序列的sort()方法中传入lambda匿名函数:

[('b',2),('a',1)].sort(key=lambda x:x[1])  # 结果为 [('a',1),('b',2)]

# 说明:使用匿名函数对列表元素进行了预处理,将本来的元组('a',1)预处理为:取出元组中后一个元素(即:1),因此可以进行排序。

 

 

 

 (2)函数参数

● 位置参数、关键字参数、返回值

在调用函数时,传入参数的顺序数量必须与函数定义匹配,不然会引起TypeError异常。若是在调用时指定参数名,这样就没必要遵守函数定义中的参数顺序了,这样可大大增长调用时的可读性。这种指定参数名传入的参数叫作关键字参数,通常的未指定参数名的参数叫作位置参数。关键字参数只能放在全部的位置参数后面。

函数参数都是按值传递的,可是若是传递的是对象(即非单纯数字),所谓按值传递就是函数参数仅仅是将对象的地址值复制一下传过去而已,因此在函数中实际上是能够改变外面对象中的内容的。通常最好避免使用这种风格,并且在涉及线程和并发的程序中,使用这类函数的效率很低,由于一般须要使用线程锁来防止反作用的影响。

若省略return语句、或单一个return关键字,就会返回 None 对象。

 

● 参数的默认值

在函数定义时,能够为某些参数指定默认值,这样在调用时能够不提供这个参数了。一旦出现带默认值的参数,此参数后续的参数都必须带默认值,不然会引起SyntaxError异常。

建议不要使用可变对象做为默认值(如空列表),这样可能致使意外的bug,以下例所示:

def fun(x, seq=[]):
    seq.append(x)
    return seq
fun(1)    # 返回[1] 
fun(2)    # 返回[1,2]
fun(3)    # 返回[1,2,3]

上例的本意是若未传入seq参数,则新建一个列表,并将x放入新列表。可是事实上会产生bug。这种状况建议使用seq=None,再在函数中建新列表。

 

● 单星号 *

在函数定义时,参数前加单星号的意思为:收集其他的位置参数,并将它们放入同一个元组中。这样在函数调用时,用户就能够提供任意多个参数了。以下例所示:

def fun(x, *y):
    print(x)
    print(y)
    
fun('a', 1, 2, 'c')   # 结果为:a 和 (1,2,'c')
fun(3)                # 结果为:3 和 ()

 

单星号亦可反转使用,即:在调用函数时使用*,自动将一个元组展开为若干个指定名字的关键字参数。

def myadd(x, y):
    return x+y
    
t = (1,2)
myadd(*t)   # 调用时,单星号自动将元组(1,2)展开为 x, y

 

● 双星号 **

在函数定义时,参数前加双星号的意思为:收集其他的关键字参数,并将它们放入一个字典中。这样在调用函数时,能够传入大量可扩充的配置项做为参数。

def fun(x, **z):
    print(x)
    print(z)

fun(x=1, y=2, z=3)    # 结果为:1 和 {'y':2, 'z':3}    

 

双星号亦可反转使用,在调用函数时使用**,自动将一个字典拆分为若干个指定名字的关键字参数。

def myadd(x, y):
    return x+y

d = {'x':1, 'y':2}
myadd(**d)    # 调用时,双星号自动将字典d展开为:x=1, y=2

 

单星号和双星号可组合使用,用于同时收集位置参数和关键字参数,**参数必须出如今*参数的后面。

def fun(*args, **kwargs):
    print(args)
    print(kwargs)
    
fun(1,2,3, x=4,y=5,z=6)
# 结果为:(1,2,3) 和 {'x':4, 'y':5, 'z':6 }

 

 

 

 (3)做用域

每次执行一个函数时,就会建立一个新的局部命名空间。这个命名空间(又叫做用域),就像其内部有一个“不可见”的字典,其中包含本函数参数的名称和全部在函数内部定义的局部变量。

除了每一个函数有有一个局部做用域之外,还有一个全局做用域。能够经过内置函数locals()和globals()查看局部和全局做用域字典,内建函数vars(obj)能够返回某个实例的做用域字典。

x = 1
def fun():
    y = 2
    print(locals())
    print(globals())

fun()

# locals()的显示为:{'y':2}
# globals()除了显示全局变量x之外,还会显示不少全局默认的变量:
{ 'x': 1,
  'fun': <function fun at 0x000001E5C52EA048>
  '__name__': '__main__',
  '__doc__':  None,
  '__package__': None,
  …… 
}

 

● 在函数内访问全局变量

Python解释器解析变量时,会先搜素局部做用域,若找不到就会搜索全局做用域,若再找不到就会搜索内置命名空间,若是仍然找不到,就会引起NameError异常。

此种方法虽然能够访问全局变量,但不能对全局变量赋值,若要赋值全局变量,须要使用global关键字。

在函数中修改全局变量:

x = 1
def fun():
    global x
    x = 2
fun()       # 运行fun()后,全局变量 x 的值变为2

函数中局部变量名和全局变量名重复时:

x = 1
def fun():
    x = 2                 # 此处建立一个名为 x 的局部变量
    globals()['x'] = 3    # 使用globals()内置函数,可经过字典的方式直接操做全局变量

fun()       # 运行fun()后,全局变量 x 的值变为3

 

注意:Python中不支持访问在函数中访问上级调用函数的局部做用域,以下例的代码会引起NameError异常

def fun_inner():
    print(x)

def fun_outer():
    x = 2
    fun_inner()
    
fun_outer()     # 调用时会引起NameError异常,由于在fun_inner()函数中,不能访问外层调用函数fun_outer()的局部做用域中的 x

 

● 嵌套做用域

Python3支持在嵌套定义的函数中,使用外层定义函数(不是外层调用函数)的局部做用域中的变量,这个称为:动态做用域(dynamic scoping),须要使用nonlocal关键字,以下例所示:

def fun_outer():
    x = 4
    def fun_inner():
        nonlocal x      # 声明绑定到外层定义的x
        x = 5
        print(x)
        
    fun_inner()
    print(x)
    
fun_outer()             #  会先运行fun_inner()中的print语句,打印出5;而后运行外层中的print语句,可看到x确实被修改为了5

 

 

 

 (4)递归

函数调用自身称为递归(recursion)。递归最典型的使用场景是,能够把一个大问题分解成重复的小问题来解决,并且这个小问题的解决结构很是简洁。虽然大部分的递归均可以用循环来替代解决,但有时用递归写函数要比循环可读性更高,看起来也更优雅。

用递归计算n的阶乘

def factorial(n):
    if n <= 1: 
        return 1
    else:
        return n * factorial(n-1)

 

用递归实现序列的二分法查找:

def search(seq, num, lower, upper):
    if lower == upper:
        return upper
    else:
        middle = (lower + upper) // 2
        if num > seq[middle]:
            return search(eq, num, middle+1, upper)
        else:
            return search(seq, num, lower, middle)
            
seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
seq.sort()
search(seq, num=2, lower=0, upper=9)    # 使用二分法查找比起遍历查找,能够很快地找到num

 

 

 

返回页首

相关文章
相关标签/搜索