希望通过博客园持续的更新,分享和记录Python基础知识到高级应用的点点滴滴!
第一波:第6章 抽象
[总览] 介绍函数、参数parameter、作用于scope概念,以及递归概念。
[6.1] 函数 - 懒惰即美德!
将程序的具体细节抽象为函数。
[6.2] 抽象和结构
抽象可以节省代码工作量,关键易使程序让人读懂。程序应该是非常抽象额,就像“下载网页、计算词频、打印单词频率”描述一样易懂。
page = download_page()
freqs = compute_frequencies(page)
for word,freq in freqs:
print word,freq
[6.3] 创建函数
函数可调用,并返回一个值。内建callable函数可判断函数是否可调用。
函数callable在Python3.0中需要使用表达式hasattr(func.__call__)代替。
使用def定义函数,return用来从函数中返回值。
def fibs(num):
result = [0,1]
for i in range(num-2):
result.append(result[-2] + result[-1])
return result
[6.3.1] 记录函数
给函数写文档,除加入注释(以#开头),还可直接在函数def语句内写上字符串。在函数的开头写下的字符串,它就会作为函数的一部分进行存储,称为文档字符串。
文档字符串可以function.__doc__方式访问。__doc__使函数属性,双下划线表示它是个特殊属性。
或者使用内建help函数,如help(function_name),可以得到关于函数,包括文档字符串的信息。
[6.3.2] 并非真正函数的函数
Python的有些函数并不返回任何东西,没有return语句或有return但后面没有跟任何值得函数,将不返回值。此时return语句只起到结束函数作用。
更准确的将,所有的函数的确都返回了东西:当不需要函数返回值的时候,函数就返回None。
[6.4] 参数魔法
[6.4.1] 值从哪里来
卸载def语句中函数名后面的变量通常叫形式参数,而调用函数的时候提供的值使实际参数,或者称为参数。
[6.4.2] 我能改变参数吗
在函数内为参数赋予新值不会改变外部任何变量的值。
def try_to_change(n):
n = 'Mr. New'
name = 'Mrs. Old'
try_to_change(name)
name
'Mrs. Old'
参数n获得了新值,但是没有影响到name变量。n实际上是个完全不同的变量。当变量n改变的时候,变量name不变。即当在函数内部把参数重绑定(赋值)的时候,函数外部的变量不会受到影响。
注意:参数存储在局部作用于local scope内。
字符串(以及数字和元组)是不可变的,考虑一下如果将可变的数据结构如列表用作参数的情况:
def change(n):
n[0] = 'Mr. Change_New'
names = ['Mrs.Entity','Mrs. Thing']
change(names)
names
['Mr. Change_New','Mrs. Thing']
当两个变量同时引用一个列表的时候,它们的确是同时引用一个列表。如果避免出现这种情况,可以复制一个列表的副本,在序列中做切片,返回的切片总是一个副本。如果复制了整个列表的切片,即得到列表的一个副本。
names = ['Mrs.Entity','Mrs. Thing']
n = names[:] #现在n和name是两个不同的列表。可通过is或==理解。原列表是安全的。
1.为什么我想要修改参数
使用函数改变数据结构是将程序抽象画的好方法。抽象的要点就是通过函数实现隐藏繁琐的细节。
def init(data):
data['first'] = {}
data['middle'] = {}
storage
{'first':{},'middle':{},'last':{}}
[6.4.3] 关键字参数和默认值
位置参数和关键字参数。慢慢习惯使用这个功能以后,就会发现程序规模越大,关键字参数的作用也越大。关键字参数主要作用在于可以明确每个参数的作用。关键词参数最厉害的地方在于可以在函数中给参数提供默认值。
位置和关键字参数可以联合使用。把位置参数放置在前面就可以。
[6.4.4] 收集参数
提供任意数量的参数是很有必要且重要的。
def print_params(*params):print params
('Testing',)
结果作为元祖打印出来。参数前的星号*将所有值放置在同一个元祖中。可以说是将这些值收集起来没然后使用。还可以联合普通参数使用。*星号的意思就是“收集其余的位置参数”。注意,此处强调位置参数。
def print_params(**params):print params
print_params(x=1,y=2,z=3)
{'x':1,'y':2,'z':3} # f返回的是字典而不是元组
def print_params(x,y,z=3,*pospar,**keypar):
print x,y,z
print pospar
print keypar
print_params(1,2,3,5,6,7,foo=1,bar=2)
1 2 3
(5,6,7)
{'foo':1,'bar':2}
[6.4.5] 反转过程
[6.4.6] 练习使用参数
位置参数,关键字参数,*,**
[6.5] 作用域
到底什么是变量?你可以把他们看作是值的名字。在执行x=1赋值语句时,名称x引用到值1。这就像用字典一样,键引用值,当然变量和所对应的值用的是个“不可见”的字典。实际上这个么说已经很接近真实情况啦。内建的vars函数可以返回这个字典:
x=1
scope = vars()
scope['x]
这类“不可见字典”叫做命名空间或者作用域。到底有多少个命名空间?除了全局作用域外,每个函数调用都会创建一个新的作用域。
def foo():x=42
x = 1
foo()
x
1
这里的foo函数改变了变量x,但是在最后的时候,x并没有变。这是因为当调用foo的时候,新的命名空间就被创建了,它作用于foo内的代码块。赋值语句x=42只在内部作用域(局部命名空间)起作用,所以它并不影响外部(全局)作用域中的x。
函数内的变量被称为局部变量local variable,这是与全局变量相反的概念。参数的工作原理类似于局部变量,所以用全局变量的名字作为参数名并没有问题。
【待深入问题:局部变量与全局变量屏蔽问题,重绑定全局变量问题,嵌套作用域问题】
[6.6] 递归
函数可以调用自身。递归简单来说就是引用自身。
递归函数包含以下几部分:
当函数直接返回值时有基本实例(最小可能性问题);
递归实例,包括一个或者多个问题最小部分的递归调用。
这里的关键就是讲问题分解为小部分,递归不能永远继续下去,因为它总是以最小可能性问题结束,而这些问题又存在基本实例中的。每次函数被调用时没针对这个调用的新命名空间会被创建,意味着当函数调用“自身”时,实际上运行的是两个不同的函数(或者说是同一个函数具有两个不同的命名空间)。
[6.6.1] 两个经典:阶乘和幂
n的阶乘定义为nx(n-1)x(n-2)x...x1
1的阶乘是1;
大于1的数n的阶乘是n乘n-1的阶乘
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
计算幂,就像内建的pow函数或者**运算符一样。
对于任意数字x来说,power(x,0)是1;
对于任意大于0的数来说,power(x,n)是x乘以(x,n-1)的结果。
def power(x,n):
if n == 0:
return 1
else:
return x * power(x,n-1)
[6.6.2] 另一个经典:二元查找
二元查找binary search。
首先定义二元查找算法的递归:
如果上下限相同,那么就是数字所在位置,返回;
否则找到两者的重点(上下限的平均值),查找数字是在左侧还是在右侧,继续查找数字所在的那半部分。
def search(sequence,number,lower,upper):
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)
提示:标准库中的bisect可以非常有效地实现二元查找。
[6.6.3] 有用的函数
map、filter、reduce函数
map函数可将序列中的所有元素全部传递给一个函数:
map(str,range(10)) # equivalent to [str(i) for i in range(10)]
filter函数可以基于一个返回布尔值的函数对元素进行过滤:
filter(func,seq)
lambda表达式,可以创建短小的函数。
filter(lambda x: x.isalnum(),seq)
reduce函数会将序列的前两个元素与给定的函数联合使用,并且将他们的返回值和第三个元素继续联合使用,直到整个序列都处理完毕,并且得到一个最终结果。
numbers = [77,102,33,45,23,62,89,201]
reduce(lambda x,y:x+y,numbers)
[6.7] 小结
抽象:抽象是隐藏多余细节的艺术。定义处理细节的函数可以让程序更抽象。
函数定义:函数使用def语句定义。由语句组成的块,可以从”外部“获取参数,可以返回一个或者多个结果。
参数:即函数调用时设定的变量。python中有两类参数:位置参数和关键字参数。
作用域:变量存储在作用(也叫命名空间)中。两类作用域:全局作用域和局部作用域,作用域可嵌套。
递归:调用函数自身。
函数式编程:Python有一些函数型编程的机制。包括lambda表达式以及map、filter、reduce函数。