Python学习笔记-第六章 抽象


本章会介绍关于抽象的常见知识及函数的特殊知识。

第六章 抽象

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章