函数

调用函数

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

定义函数

空函数

如果想定义一个什么事也不做的空函数,可以用pass语句:
好处:现在还没想好怎么写函数的代码,可以先放一个pass,让代码能运行起来。

def nop():
    pass

函数体内部可以用return随时返回函数结果;
函数执行完毕也没有return语句时,自动return None

练习:

import math

def quadratic(a, b, c):
    x1 = (-b+math.sqrt(b*b-4*a*c))/(2*a)
    x2 = (-b-math.sqrt(b*b-4*a*c))/(2*a)
    return x1,x2
print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))

if quadratic(2, 3, 1) != (-0.5, -1.0):
    print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
    print('测试失败')
else:
    print('测试成功')

输出:

quadratic(2, 3, 1) = (-0.5, -1.0)
quadratic(1, 3, -4) = (1.0, -4.0)
测试成功

函数的参数

默认参数

def A(x,n):
    s = 1
    while n >= 1:
        n = n-1
        s = s *x
    return s
print(A(5,2))

25

A(x, n)函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。

def A(x,n=2):
    s = 1
    while n >= 1:
        n = n-1
        s = s *x
    return s
print(A(5,3))
print(A(5))

125
25

默认参数可以简化函数的调用。设置默认参数时,有几点要注意:

一是必选参数在前,默认参数在后,否则Python的解释器会报错;
二是如何设置默认参数。
当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
使用默认参数的好处:最大的好处是能降低调用函数的难度。
注意示例:

def enroll(name, gender, age=6, city='Beijing'):
    print('name:', name)
    print('gender:', gender)
    print('age:', age)
    print('city:', city)
    return name, gender, age,city
print(enroll('A','M',10))
print(enroll('A','M',city='TianJin'))

name: A
gender: M
age: 10
city: Beijing
('A', 'M', 10, 'Beijing')
name: A
gender: M
age: 6
city: TianJin
('A', 'M', 6, 'TianJin')

分析:
(1)按顺序提供默认参数:比如调用enroll(‘Bob’, ‘M’, 7),意思是,除了name,gender这两个参数外,最后1个参数应用在参数age上,city参数由于没有提供,仍然使用默认值。
(2)不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll(‘A’,‘M’,city=‘TianJin’),意思是,city参数用传进去的值,其他默认参数继续使用默认值。
最大的坑:

def add_end(L=[]):
    L.append('END')
    return L
print(add_end())
print(add_end())

['END']
['END', 'END']

原因:
Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
定义默认参数要牢记一点:默认参数必须指向不变对象!
修改为:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L
print(add_end())
print(add_end())

['END']
['END']

原因:
不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。

可变参数

可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。

def A(*num):
    sum = 0
    for n in num:
        sum = sum + n*n
    return sum
print(A(1,3,5,7))
print(A())

84
0

*nums表示把nums这个list的所有元素作为可变参数传进去。

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
下面是三种写法的示例:

def person(name,age,**other):
    print('name:', name, 'age:', age, 'other:', other)
    return 0
extra = {'gender' : 'M', 'job': 'fashi'}
print(person('diaochan',20))
print(person('diaochan',20,gender = 'M',job = 'fashi'))
print(person('diaochan',20,**extra))

name: diaochan age: 20 other: {}
0
name: diaochan age: 20 other: {'gender': 'M', 'job': 'fashi'}
0
name: diaochan age: 20 other: {'gender': 'M', 'job': 'fashi'}
0

extra表示把extra这个dict的所有key-value用关键字参数传入到函数的other参数,other将获得一个dict,注意other获得的dict是extra的一份拷贝,对other的改动不会影响到函数外的extra。

命名关键字函数

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收gender和job作为关键字参数。这种方式定义的函数如下:

def person(name,age,*,gender,job):
    print('name:', name, 'age:','gender:',gender, age, 'job:', job)
    return 0
print(person('diaochan',20,gender = 'M',job = 'fashi'))

name: diaochan age: gender: M 20 job: fashi
0

和关键字参数**other(教程上是 **kw ,都一样,只是命名不同)不同,命名关键字参数需要一个特殊分隔符 * , * 后面的参数被视为命名关键字参数。
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

def person(name,age,*,gender,job):
    print('name:', name, 'age:','gender:',gender, age, 'job:', job)
    return 0
print(person('diaochan',20, 'M','fashi'))

Traceback (most recent call last):
  File "d:/python exercise/1.py", line 4, in <module>
    print(person('diaochan',20, 'M','fashi'))
TypeError: person() takes 2 positional arguments but 4 were given

缺省值:

def person(name,age,*,gender,job  = 'fashi'):
    print('name:', name, 'age:','gender:',gender, age, 'job:', job)
    return 0
print(person('diaochan',20, gender = 'M'))

name: diaochan age: gender: M 20 job: fashi
0

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符 * 了:

def person(name, age, *args, city, job):
    print(name, age, args, city, job)

注意:
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个 * 作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数。

def person(name, age, city, job):
    # 缺少 *,city和job被视为位置参数
    pass

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。
但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
print(f1(1,2))
print(f1(1,2,c = 3))
print(f1(1,2,3,'a','b'))
print(f1(1,2,3,'a','b', x = 999))
print(f2(1,2,d = 99,e = None))

a = 1 b = 2 c = 0 args = () kw = {}
None
a = 1 b = 2 c = 3 args = () kw = {}
None
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
None
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 999}
None
a = 1 b = 2 c = 0 d = 99 kw = {'e': None}
None

最神奇的是通过一个tuple和dict,你也可以调用上述函数:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
args1 = (1, 2, 3, 4)
kw1 = {'d': 99, 'x': '#'}
args2 = (1, 2, 3)
kw2 = {'d': 99, 'x': '#'}
print(f1(*args1,**kw1))
print(f2(*args2,**kw2))

a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
None
a = 1 b = 2 c = 3 d = 99 kw = {'x': '#'}
None

若abcde,abc分别等于123,d就在赋值的参数那继续往后读取
注意:
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。

递归函数

例子,计算阶乘:

def fact(n):
    if n ==1:
        return 1
    return n*fact(n-1)
print(fact(1))
print(fact(5))

1
120

执行过程:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

递归调用的次数过多,会导致栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
改成尾递归函数:

def fact(n):
    return fact_iter(n,1)
def fact_iter(num,product):
    if num ==1:
        return product
    return fact_iter(num-1,num * product)
print(fact(5))

120

调用过程:

===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
尾递归调用时做优化就不会导致栈溢出,但是python标准解释器没有做优化所以还存在栈溢出的问题。
汉诺塔的例子:

def move(n, a, b, c):   
    if n == 1:
        print(a, '-->', c)
    else:
        move(n-1, a, c, b)  
        print(a, '-->', c)
        move(n-1, b, a, c)    
print(move(3, 'A', 'B', 'C'))

A --> C
A --> B
C --> B
A --> C
B --> A
B --> C
A --> C
None
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值