-
定义函数
在Python中,定义一个函数要使用def
语句,如定义一个求绝对值的函数
def my_abs(x):
if x>0:
return x
else:
return -x
如果你已经把my_abs()
的函数定义保存为abstest.py
文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs
来导入my_abs()
函数
导入时可能出现的错误
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'absstest'
- 空函数
def nop():
pass
实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass
,让代码能运行起来。缺少了pass
代码就会有语法错误
my_abs()
函数和内置函数abs()
的区别:
当传入了不恰当的参数时,内置函数abs
会检查出参数错误,而我们定义的my_abs
没有参数检查,会导致if
语句出错,出错信息和abs
不一样。
此时我们修改my_abs
的定义,对参数类型做检查,只允许整数和浮点数类型的参数
def my_abs(x):
if not isinstance(x,(int,float)):
raise TypeError('bad operand type')
if x>=0:
return x
else:
return -x
- 返回多个函数值
import math
def move(x,y,step,angle=0):
nx=x+step*math.cos(angle)
ny=y-step*math.sin(angle)
return nx,ny
>>> x,y=move(100,100,60,math.pi/6)
>>> print(x,y)
151.96152422706632 70.0
实际上python返回的仍是单一值,Python的函数返回多值其实就是返回一个tuple
>>> r=move(100,100,60,math.pi/6)
>>> print(r)
(151.96152422706632, 70.0)
-
小结
小结
定义函数时,需要确定函数名和参数个数;
如果有必要,可以先对参数的数据类型做检查;
函数体内部可以用return随时返回函数结果;
函数执行完毕也没有return语句时,自动return None。
函数可以同时返回多个值,但其实就是一个tuple。
写一个计算x^n的函数
>>> def power(x,n):
... s=1
... while n>0:
... n=n-1
... s=s*x
... return s
...
>>> power(5,3)
125
- 设置默认参数
>>> def power(x,n=3):
... s=1
... while n>0:
... n=n-1
... s=s*x
... return s
...
>>> power(5)
125
这样,当我们调用power(5)时,相当于调用power(5, 3)
默认参数可以简化函数的调用。
只有与默认参数不符的学生才需要提供额外信息
def enroll(name,gender,age=6,city='Beijing'):
print('name=',name)
print('gender=',gender)
print('age=',age)
print('city=',city)
>>> enroll('Bob','M',7)
name= Bob
gender= M
age= 7
city= Beijing
设置默认参数时,有几点要注意:
一是必选参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在必选参数前面);
二是如何设置默认参数。
当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
默认参数的一个大坑
def add_end(L=[]):
L.append('End')
return L
>>> add_end()
['End']
>>> add_end()
['End', 'End']
默认参数是[]
,但是函数似乎每次都“记住了”上次添加了'END'
后的list
。这是因为L指向的是一个list,list是可变对象。定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上述例子,可以用None
这个可变对象来实现
def add_end(L=None):
if L is None:
L=[]
L.append('End')
return L
>>> add_end()
['End']
>>> add_end()
['End']
-
定义可变参数
给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。
def calc(numbers):
sum=0
for x in mumbers:
sum=sum+x*x
return sum
这样定义需要事先传递一组list或tuple
>>> calc([1,2,3])
14
>>> calc((4,5,6))
77
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。
def calc(*numbers):
sum=0
for n in numbers:
sum=sum+n*n
return sum
这样我们调用时只需要输入
>>> calc(4,5,6)
77
如果已经有一个list或者tuple,要调用一个可变参数怎么办?
>>> nums=[1,2,3]
>>> calc(nums[0],nums[1],nums[2])
14
>>> calc(*nums)
14
显然第二种方法比较简便。*nums
表示把nums
这个list的所有元素作为可变参数传进去
-
定义关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
def person(name,age,**kw):
print('name:',name,'age:','others:',kw)
>>> person('cloris',21,city='Beijing')
name: cloris age: others: {'city': 'Beijing'}
>>> person('cloris',21,city='Beijing',school='RUC',gender='F')
name: cloris age: others: {'city': 'Beijing', 'school': 'RUC', 'gender': 'F'}
如果已经有一个dict,要调用一个关键字参数怎么办?
>>> extra={'city':'Beijing','job':'Engineer'}
>>> person('cloris',21,city=extra['city'],job=extra['job'])
name: cloris age: others: {'city': 'Beijing', 'job': 'Engineer'}
>>> person('cloris',21,**extra)
name: cloris age: others: {'city': 'Beijing', 'job': 'Engineer'}
-
命名关键字参数
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。
def person(name, age, *,city,job):
print(name,age,city,job)
>>> person('cloris',21,city='Beijing',job='engineer')
cloris 21 Beijing engineer
注:命名关键字参数必须传入参数名,如上面必须写成city=
,job=
如果没有传入参数名,调用将报错
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了:
def person(name, age, *args,city,job):
print(name,age,args,city,job)
命名关键字参数有时可以缺省,如:
>>> def person(name, age,*,city='Beijing',job):
... print(name,age,city,job)
...
>>> person('cloris',21,job='engineer')
cloris 21 Beijing engineer
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*
作为特殊分隔符。如果缺少*
,Python解释器将无法识别位置参数和命名关键字参数
-
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
比如定义一个函数,包含上述若干种参数:
def f(a,b,c=0,*args,**kw):
print('a:',a,'b:',b,'c:',c,'args:',args,'kw:',kw)
def f(a,b,c=0,*,d,**kw):
print('a:',a,'b:',b,'c:',c,'d:',d,'kw:',kw)
>>> f1(1,2,3,'a','b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1,2,3,'a','b',x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1,2,3,d=99,ext=None)
a = 1 b = 2 c = 3 d = 99 kw = {'ext': None}
对于任意函数,都可以通过类似func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
>> args=(1,2,3,4)
>>> kw={'d':99,'x':'#'}
>>> f1(*args,**kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
例:输出以下成绩单
>>> print_scores(Adam=99,Lisa=88,Bart=77)
Name Score
-----------------
Adam 99
Lisa 88
Bart 77
def print_scores(**kw):
print(' Name Score')
print('-----------------')
for name,score in kw.items():
print('%10s %d'%(name,score))
print()
>>> data={'Adam Lee':90,'Lisa S':88,'F.Bart':77}
>>> print_scores(**data)
Name Score
-----------------
Adam Lee 90
Lisa S 88
F.Bart 77
例:输出以下格式
Personal Info
---------------
Name: Bob
Gender: male
City: Beijing
Age: 20
def print_info(name, *, gender, city='Beijing', age):
print('Personal Info')
print('----------------')
print(' Name:',name)
print(' Gender:',gender)
print(' City:',city)
print(' Age:',age)
print()
>>> print_info('Bob',gender='M',age=21)
Personal Info
----------------
Name: Bob
Gender: M
City: Beijing
Age: 21
例:编写一个函数,输出Hello, Michael, Bob, Adam!
def hello(greeting, *args):
if(len(args)==0):
print('%s!'%greeting)
else:
print('%s,%s!'%(greeting,','.join(args)))
>>> hello('Hello','Michael','Bpb','Adam')
Hello,Michael,Bpb,Adam!
-
递归函数
写一个递归函数计算n!
def fact(n):
if n==1:
return 1
return n*fact(n-1)
>>> fact(5)
120
递归调用的次数过多,会导致栈溢出。可以试试fact(1000)
:
>>> fact(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in fact
File "<stdin>", line 4, in fact
File "<stdin>", line 4, in fact
[Previous line repeated 989 more times]
File "<stdin>", line 2, in fact
RecursionError: maximum recursion depth exceeded in comparison
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
def fact(n):
return fact_iter(n,1)
def fact_iter(num,product):
if product==1:
return product
return fact_iter(num-1,num*product)
例:汉诺塔圆盘的移动。请编写move(n, a, b, c)
函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法
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)
>>> move(3, 'A', 'B', 'C')
A --> C
A --> B
C --> B
A --> C
B --> A
B --> C
A --> C
-
切片
例1:取一个list或tuple的部分元素
常规方法:
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>> L[0],L[1],L[2]
('Michael', 'Sarah', 'Tracy')
- 使用循环
r=[]
n=3
for i in range(3):
r.append(L[i])
>>> r
['Michael', 'Sarah', 'Tracy']
Python提供了切片(Slice)操作符,能大大简化这种操作。
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']
>>> L[:3]
['Michael', 'Sarah', 'Tracy']
L[0:3]
表示,从索引0
开始取,直到索引3
为止,但不包括索引3
。即索引0
,1
,2
,正好是3个元素
倒数切片
>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']
取前十个数
>>> L=list(range(100))
>>> L[:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
取后十个数
>>> L[-10:]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
前十个数每两个取一个
>>> L[:10:2]
[0, 2, 4, 6, 8]
所有数每5个取一个
>>> L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
只写[:]
就可以原样复制一个list
>>> L[:]
tuple
,字符串'xxx'
均可看成一个list
,都可以进行切片操作
>>> (0,1,2,3,4,5)[:3]
(0, 1, 2)
>>> 'ASDFGH'[:3]
'ASD'
>>> 'ASDFGHWERT'[::2]
'ADGWR'
例:利用切片操作,实现一个trim()函数,去除字符串首尾的空格
def trim(s):
if s != None:
while s[:1]==' ':
s=s[1:]
while s[-1:] ==' ':
s=s[:-1]
return s
-
迭代
如果给定一个list
或tuple
,我们可以通过fo
r循环来遍历这个lis
t或tuple
,这种遍历我们称为迭代(Iteration)
dict
也是可以迭代的
>>> d={'a':1,'b':2,'c':3}
>>> for x in d:
... print(x)
...
a
b
c
注:默认情况下,dict
迭代的是key
。如果要迭代value
,可以用for value in d.values()
,如果要同时迭代key
和value
,可以用for k, v in d.items()
字符串也是可迭代对象,也可作用于for循环
如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:
>>> from collections import Iterable
>>> isinstance('abc',Iterable)
True
>>> isinstance(123,Iterable)
False
在for循环中同时迭代索引和元素本身:
Python内置的enumerate
函数可以把一个list
变成索引-元素对
>>> for i,value in enumerate(['A','B','C']):
... print(i,value)
...
0 A
1 B
2 C
for
循环里可同时引入两个变量
>>> for x,y in [(1,1),(2,3),(4,5)]:
... print(x,y)
...
1 1
2 3
4 5
例:请使用迭代查找一个list
中最小和最大值,并返回一个tuple
:
def findMinAndMax(L):
if L:
min=max=L[0]
for x in L:
if x<min:
min=x
if x>max:
max=x
return (min,max)
else:
return (None,None)
-
列表生成式
如果要生成[1x1, 2x2, 3x3, ..., 10x10]
怎么做?
方法一:循环
>>> L=[]
>>> for x in range(1,11):
... L.append(x*x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
方法二:列表生成式
>>> [x*x for x in range(1,11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
要生成的元素x*x
放到for
的前面
for
循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
>>> [x*x for x in range(1,11) if x%2==0]
[4, 16, 36, 64, 100]
还可以使用两层循环,可以生成全排列:
>>> [m+n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
dict
的items()
可以同时迭代key
和value
:
>>> d={'x':'A','y':'B','z':'C'}
>>> for k,v in d.items():
... print(k,'=',v)
...
x = A
y = B
z = C
利用列表生成式使用两个变量生成一个list
>>> [k+'='+v for k,v in d.items()]
['x=A', 'y=B', 'z=C']
例:把一个list中所有的字符串变成小写:
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']
如果list中含有非字符型数据,则列表生成式会报错,此时我们可以做如下修改:
>>> L1 = ['Hello', 'World', 18, 'Apple', None]
>>> L2=[s.lower() for s in L if isinstance(s,str)]
>>> L2
['hello', 'world', 'ibm', 'apple']
-生成器
如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建一个generator的方法:
方法一:
>>> g=(x*x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000026A8B999F48>
可以通过next()
一个一个打印出generator中每一个元素,generator
保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
使用next()
一个一个打印太麻烦了,为此我们可以写一个循环来抛出所有元素
>>> g=(x*x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
方法二:带yield
的generator function
例:写一个斐波那契数列
def fib(max):
n,a,b=0,0,1
while n<max:
print(b)
a,b=b,a+b
n=n+1
return 'done'
>>> fib(5)
1
1
2
3
5
'done'
要把fib
函数变成generator
,只需要把print(b)
改为yield b
就可以了:
def fib(max):
n,a,b=0,0,1
while n<max:
yield b
a,b=b,a+b
n=n+1
return 'done'
>>> fib(5)
<generator object fib at 0x0000026A8B999F48>
要获取返回值可以用next()
一个一个打印
>>> g=fib(5)
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
也可以用一个for
循环打印
>>> for n in fib(5):
... print(n)
...
1
1
2
3
5
例:把杨辉三角形的每一行看做一个list,试写一个generator,不断输出下一行的list:
def triangles():
p=[1]
while True:
yield p
p=[1]+[p[x]+p[x+1] for x in range(len(p)-1)]+[1]
-迭代器
可迭代对象(Iterable
):可以直接作用于for
循环的对象
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function
。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
>>> isinstance(iter([]),Iterator)
True
>>> isinstance(iter('ABC'),Iterator)
True