本章会介绍关于抽象的常见知识及函数的特殊知识。
第六章 抽象
6.1 懒惰即美德
一段代码只用编写一次,下次使用的时候就可以直接拿出来用。用抽象的方式来完成,只需要告诉计算机去做,不用特别说明怎么做。这就是程序员的偷懒方法。
6.2 抽象和结构
抽象可以节省很多工作,并且使得程序更容易被读懂。就像我们告诉计算机下载网页,计算词频,而不用说明具体的细节,细节部分会在函数定义中完成。
6.3 创建函数
函数是可以调用的,执行某种行为并返回一个值,可能包含参数,就是放在圆括号中的值。内建的callable函数可以判断函数是否可调用 。
import math
x=1
y=math.sqrt
callable(x) #返回值False
callable(y) #返回值True
创建函数要用到def语句。
def hello(name):
return 'Hello,'+name+'!' #运行后就会创建一个名为hello的函数。下面就可以直接调用了。
print(hello('world')) #返回Hello,world!
print(hello('Gumby')) #返回Hello,Gumby!
#教电脑怎么运算斐波那契数列
def fibs(num):
result=[0,1]
for i in range(num-2):
result.append(result[-2]+result[-1])
return result #一定要注意缩进格式,写错了不仅代码看起来很乱,运行也会出错。
#下面就可以直接用fibs函数了
fibs(10) #返回包含10个数的斐波那契数列
6.3.1 记录函数
给函数写说明文档,可以加入注释(#开头的语句),还有一个方式是直接写上字符串,如果在函数开关写下字符串,它会作为函数的一部分进行存储,这称为文档字符串。
def square(x):
'Calculates the square of the number x.' #文档字符串
return x*x
#访问文档字符串
square.__doc__ #doc前后分别是两个下划线。
help(square) #可以得到更详细的函数信息。
6.3.2 并非真正函数的函数
python中有些函数不返回任何东西,不是学术意义上的函数。比如没有return语句,或有return语句,但后面没有任何值。
当函数没有返回值时,会返回None.
def test():
print('this is printed')
return #return只起到结束函数的作用
print('This is not')
x=test() #返回值 this is printed
x #什么都没有
print(x) #返回None
6.4 参数魔法
6.4.1 值从哪里来
写在def语句中函数名后面的变量通常叫作函数的形式参数,而调用函数时提供的值是实际参数,或称为参数。本书会将实际参数称为值以区别于形式参数。
6.4.2 我能改变参数吗
参数只是变量,在函数内为参数赋予新值不会改变外部任何变量的值。
def try_to_change(n):
n='Mr.Gumby'
name='Mrs. Entity'
try_to_change(name)
name #返回值'Mrs. Entity',虽然try_to_change函数中,参数n获得了新值,但是对name变量没有影响。
name='Mrs.Entity'
n=name #用name给n赋值
n='Mr.Gumby'
name #返回值'Mrs.Entity'#n改变,变量name依然不变。
在函数内部把参数重绑定(赋值)的时候,函数外的变量不会受到影响。
字符串本身是不可变的,如果将列表用作参数时会发生什么?
def change(n):
n[0]='Mr. Gumby'
names=['Mrs.Entity','Mrs.Thing']
change(names)
names #返回值['Mr. Gumby', 'Mrs.Thing'],参数值改变了
当两个变量同时引用一个列表的时候,对同一个列表作出了改动。为了避免这个情况,可以复制一个列表的副本,当在序列中做切片的时候,返回的切片总是一个副本。
names=['Mrs.Entity','Mrs.Thing']
n=names[:]
n is names #False,n是name的副本
n==names #True,两个参数值相等
n[0]='Mr Gumby'
n #返回值['Mr Gumby', 'Mrs.Thing']
names #返回值['Mrs.Entity', 'Mrs.Thing']。names没有被改变。
change(names[:])
names #返回值['Mrs.Entity', 'Mrs.Thing']。names没有被改变。
1.为什么要修改参数
要更新列表的时候需要一项一项更新很麻烦,而且可读性差。
抽象的要点就是隐藏更新时的烦琐细节。
def init(data):
data['first']={}
data['middle']={}
data['last']={}
storage={}
init(storage)
storage #返回值{'first': {}, 'middle': {}, 'last': {}}
在编写存储名字的函数前,先写个获得名字的函数:
def lookup(data,label,name):
return data[label].get(name)
标签以及名字可以作为参数提供给lookup函数使用,这样会获得包含命名的列表。
在名字已经存储的情况下可以用下面的代码获取:
lookup(storage,'middle','lie') #返回中间名是lie的数据
返回的列表和存储在数据结构中的列表是相同的,如果列表被修改了,也会影响数据结构。
#编写可以查询人名的函数
def init(data):#data作为存储表,初始化,data中的存储格式为{‘first':{},'middle':{},'last':{})
data['first']={}
data['middle']={}
data['last']={}
def lookup(data, label, name):
return data[label].get(name) #get方法根据键找到值
def store(data,full_name):
names=full_name.split()#split方法,加入分隔符,默认为空格
if len(names)==2:names.insert(1,'')#如没有中间名插入空格
labels='first','middle','last'
for label,name in zip(labels,names):#zip内建函数,支持将可迭代对象作为参数,将对象中的元素打包成一个个元组,返回由这些元组组成的列表
people=lookup(data,label,name)
if people: #这里一定要注意缩进!!!
people.append(full_name) #如果对应的label(first,middle,last)的name已经存在,就把全名添加进去
else:
data[label][name]=[full_name]#键不存在时,自动添加键值
MyNames={}
init(MyNames)
store(MyNames,'Magnet Lie Hetland')
lookup(MyNames,'middle','Lie') #返回值['Magnet Lie Hetland'].
#zip举例
a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
zip(a,c) #python2.x 返回[(1, 4), (2, 5), (3, 6)],新版本返回<zip object at 0x00000000035F4808>
2.如果我的参数不可变呢
Python中,函数只能修改参数对象本身,但是如果你的参数不可变(比如是数字),那就没有办法了。
这时就要从函数中返回所有你需要的值(值多于一个就以元组形式返回)。
#将变量数值增加1
def inc(x):
return x+1
foo=10
foo=inc(foo)
foo #返回11.
#将值放在列表中
def inc(x):
x[0]=x[0]+1
foo=[10]
inc(foo)
foo #返回[11]
6.4.3 关键字参数和默认值
目前为止我们所使用的参数都叫做位置参数,位置甚至比名字更重要。本节中引入的这个功能可以回避位置问题。
#下面的两个函数hello_1和hello_2实际上是一样的,虽然参数名字不一样,决定结果是参数的顺序。
def hello_1(greeting,name):
print('%s,%s!'%(greeting,name))
def hello_2(name,greeting):
print('%s,%s!'%(name,greeting))
hello_1('Hello','World')
hello_2('Hello','World') #只要这里的参数顺序一样,显示的结果都一样。
可以通过提供参数的名字来避免弄混参数顺序:
hello_1(name='World',greeting='Hello') #返回Hello,World!,虽然顺序不一样,显示的顺序还是对的。
这类使用参数名提供的参数叫关键字参数。还可以在函数中给参数提供默认值。
def hello_3(greeting='Hello',name='World'):
print('%s,%s!' %(greeting,name))
hello_3() #返回默认值Hello,World!
hello_3('Greetings') #返回Greetings,World!
hello_3('Greeting','Universe') #返回Greeting,Universe!
hello_3(name='World') #返回Greeting,World!
#可以允许用户任意组合名字,问候语和标点
def hello_4(name,greeting='hello',punctuation='!'):
print('%s,%s%s' % (greeting,name,punctuation))
hello_4('Mars') #返回hello,Mars!
hello_4('Mars','Howdy','...') #返回Howdy,Mars...
hello_4('Mars',punctuation='.') #返回hello,Mars.
hello_4('Mars',greeting='Top of the morning to ya') #返回Top of the morning to ya,Mars!
hello_4() #错误用法,name没有设定默认值
6.4.4 收集参数
在参数前加星号(*)可以打印出元组,参数前的星号将所有的值收集起来使用。
def print_params(*params):
print(params)
print_params(1,2,3) #返回元组(1, 2, 3)
print_params('testing') #返回长度为1的元组('testing',)
可以联合普通参数
def print_params_2(title,*params):
print(title)
print(params)
print_params_2('Params:',1,2,3) #返回值Params:(1, 2, 3)
print_params_2('Nothing:') #返回Nothing:()
**返回的是字典
def print_param_3(**params):
print(params)
print_param_3(x=1,y=2,z=3)
{'y': 2, 'z': 3, 'x': 1}
def print_param_4(x,y,z=3,*pospar,**keypar):
print(x,y,z)
print(pospar)
print(keypar)
print_param_4(1,2,3,4,5,6,7,foo=1,bar=2) #返回1 2 3,(4, 5, 6, 7),{'foo': 1, 'bar': 2}
#修改6.4.2中的例子,将store函数改为如下格式
def store(data,*full_names):
for full_name in full_names:
names=full_name.split()
if len(names)==2:names.insert(1,'')
labels='first','middle','last'
for label,name in zip(labels,names):
people=lookup(data,label,name)
if people:
people.append(full_name)
else:
data[label][name]=[full_name]
#调用可以更方便
d={}
init(d)
store(d,'Luke Skywalker','Anakin Skywalker')
lookup(d,'last','Skywalker') #返回值['Luke Skywalker', 'Anakin Skywalker']
6.4.5 反转过程
收集参数的逆过程,即分配参数。是在调用的时候使用。
def add(x,y):return x+y
param=(1,2)
add(*param) #返回3.
def hello_3(greeting='Hello',name='World'):
print('%s,%s!' %(greeting,name))
params={'name':'Sir Robin','greeting':'Wellmet'}
hello_3(**params) #返回Wellmet,Sir Robin!
def with_stars(**kwds):print(kwds['name'],'is',kwds['age'],'years old')
def without_stars(kwds):print(kwds['name'],'is',kwds['age'],'years old')
args={'name':'Mr.Gumby','age':42}
with_stars(**args)
without_stars(args) #两个函数的返回值一致,都是Mr.Gumby is 42 years old
在with_star中,定义和调用函数时都使用了星号。星号只有在定义函数(允许使用不定数目的参数)或者调用(“分割”字典或者序列)时才有用。
6.4.6 练习使用参数
def story(**kwds):
return 'Once upon a time, there was a '\
'%(job)s called %(name)s.' %kwds
def power(x,y,*others):
if others:
print('Received redundant parameters:',others)
return pow(x,y)
def interval(start,stop=None,step=1):
'Imitates range() for step>0'
if stop is None:
start.stop=0,start
result=[]
i=start
while i<stop:
result.append(i)
i+=step
return result
print(story(job='king',name='gumby')) #返回Once upon a time, there was a king called gumby.
print(story(name='Sir Robin',job='brave knight')) #返回Once upon a time, there was a brave knight called Sir Robin.
params={'job':'language','name':'Python'}
print(story(**params)) #返回Once upon a time, there was a language called Python.
power(2,3) #返回8
power(3,2) #返回9
power(y=3,x=2) #返回8
params=(5,)*2 #值为(5,5)
power(*params) #返回3125
power(3,3,'Hello,World') #返回Received redundant parameters: ('Hello,World',) 27
interval(10) #报错'int' object has no attribute 'stop'
interval(1,5) #返回[1, 2, 3, 4]
interval(3,12,4) #返回[3, 7, 11]
power(*interval(3,7)) #返回Received redundant parameters: (5, 6) 81。interval(3,7)得到[3,4,5,6],power函数里将3,4赋值给x,y,(5,6)给other.
6.5 作用域
变量可以看作是值的名字,变量到值就像字典一样,但是不可见。这类“不可见字典”叫做命名空间或作用域。vars函数可以返回作用域。
x=1
scope=vars()
scope['x'] #返回值1
scope['x']+=1
x #返回2
def foo():x=42
x=1
foo()
x #返回1,x=42只在内部作用域起作用,不影响外部x的值。
def combine(parameter):print(parameter+external)
external='berry'
combine('Shrub') #返回Shrubberry
在函数内部赋予一个变量,它会自动成为局部变量,除非告知python将其声明为全局变量
x=1
def change_global():
global x #声明x为全局变量
x=x+1
change_global()
x #返回2
嵌套作用域
嵌套一般来说没什么用,有一个很突出的应用:需要用一个函数创建另一个
def multiplier(factor):
def multiplyByFactor(number):
return number*factor
return multiplyByFactor
double=multiplier(2)
double(5) #返回10
triple=multiplier(3)
triple(3) #返回9
multiplier(5)(4) #返回20
每次调用外部函数,内部的函数都被重新绑定,multiplyByFactor函数存储子封闭作用域的行为叫闭包。
6.6 递归
递归就是只引用自身。
有用的递归函数包含下面几部分:
1.当函数直接返回值时有基本实例
2.递归实例,包括一个或者多个问题最小部分的递归调用。
6.6.1 两个经典:阶乘和幂
#阶乘的递归版本
def factorial(n):
if n==1:
return 1
else:
return n*factorial(n-1)
#计算幂的递归版本
def power(x,n):
if n==0:
return 1
else:
return x*power(x,n-1)
大多数情况下,递归可以用循环来代替,大多数时候更高效,递归更易读懂。
6.6.2 另外一个经典:二元查找
def search(sequence,number,lower=0,upper=None): #int格式不能使用None,新版本怎么修改呢?
if lower==upper:
assert number==sequence[upper]
return upper
else:
middle=(lower+upper)//2
if number>sequence[middle]:
return search(sequence,number,middle+1,upper)
else:
return search(sequence,number,lower,middle)
内建函数的用法和含义可以在官方网站上查到:https://docs.python.org/2/library/functions.html#built-in-functions