Python函数,高级特性

  • 定义函数
    在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')
  1. 使用循环
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。即索引012,正好是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
  • 迭代
    如果给定一个listtuple,我们可以通过for循环来遍历这个list或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(),如果要同时迭代keyvalue,可以用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']

dictitems()可以同时迭代keyvalue

>>> 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

方法二:带yieldgenerator 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循环的对象
一类是集合数据类型,如listtupledictsetstr等;
一类是generator,包括生成器和带yieldgenerator function
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator
listdictstrIterable变成Iterator可以使用iter()函数:

>>> isinstance(iter([]),Iterator)
True
>>> isinstance(iter('ABC'),Iterator)
True
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容