python基础语言与应用第五章_Python基础教程读书笔记(第5章—第6章:条件、循环和其他语句;抽象)...

第五章:条件、循环和其他语句

1:print 和 import 的更多信息

1)使用逗号输出——打印多个表达式,只要将它们用逗号隔开就行:

>>> print('Age:',42)

Age:42

注意:print的参数并不能像我们预期那样构成一个元组(print在3.X中并不是语句,而是函数):

>>> print(1,2,3)1 2 3

>>> print((1,2,3))

(1, 2, 3)

>>> name = 'Aaron'

>>> salutation = 'Mr.'

>>> greeting = 'Hello,'

>>> print(greeting,salutation,name)

Hello, Mr. Aaron>>> print(greeting + ',', salutation,name) #在字符串外添加字符

Hello,, Mr. Aaron

2)把某件事作为另一件事导入

从模块导入函数时,可以使用:

import somemodule

或者

from somemodule import somefunction

或者

from somemodule import somefunction,anotherfunction,yetanotherfunction

或者

from somemodule import *

只有确定要从给定模块导入所有功能时,才应该使用最后一个版本。

如果两个模块都有open函数,则使用第一种方式导入,然后:

module1.open()、module2.open() 这样使用函数

或者:在语句末尾增加一个as字句,在该字句后给出名字,或为整个模块提供别名

>>> importmath as foobar>>> foobar.sqrt(4)2.0

#from math import sqrt as foobar 为函数提供别名

2:赋值魔法——赋值语句的特殊技巧

1)序列解包(sequence unpacking)——或:可选代解包——将多个值的序列解开,然后放到变量的序列中

>>> x,y,z=1,2,3

>>> print(x,y,z)1 2 3

>>> x,y=y,x #用它交互两个(或更多)变量也没问题

>>> print(x,y,z)2 1 3

更形象一点的表示出来就是:

>>> values = 1,2,3

>>>values

(1, 2, 3)>>> x,y,z=values>>>x1

当函数或者方法返回元组(或者其他序列或可迭代对象)时,这个特性尤其有用。假设需要获取(和删除)字典中任意的键值对,可以使用popitem方法,这个方法将键-值 作为元组返回。那么这个元组就可以直接赋值到两个变量中:

>>> scoundrel = {'name':'Robin','girlfriend':'Marion'}>>> key,value =scoundrel.popitem()>>>key'girlfriend'

>>>value'Marion'

注意:所解包的序列中的元素数量必须和放置在赋值符号=左边的变量数量完全一致,否则会出错

Python3.0中有另外一个解包特性:可以像在函数的参数列表中一样使用星号运算符(参加第六章)。例如:a,b,rest* = [1,2,3,4]最终会在a和b都被赋值之后将所有其他的参数都收集到rest中。rest结果将是[3,4]。使用星号的变量也可以放在第一个位置,这样它就总会包含一个列表。

2)链式赋值(chained assignment)是将同一个值赋给多个变量的捷径。它看起来像上面的并行赋值,不过这里只处理一个值:

x=y=somefunction() 和下面的语句的效果是一样的

y = somefunction()

x=y

注意上面的语句和下面的语句不一定等价:

x=somefunction()

y=somefunction()

3)增量赋值——对于*、/、%等标准运算符都适用

>>> x=2

>>> x+=1

>>> x*=2

>>>x6

对于其他数据类型也适用(只要二元运算符本身适用于这些数据类型即可)

>>> fnord='foo'

>>> fnord += 'bar'

>>> fnord *= 2

>>>fnord'foobarfoobar'

3:语句块——缩排的乐趣(一个tab为8个空格,python使用空格缩进语句块,使用冒号:来标识语句块开始)

4:条件和条件语句

1)布尔变量——下面的值作为布尔表达式的时候,会被解释为假(false):

False、None、0、""、()、[]、{}

也就是说:标准值Flase和None、所有类型的数字0(浮点型、长整型和其他类型等)、空序列(如空字符串、元组和列表)以及空字典都为假。其他解释为真

bool函数可以用来(和list、str、tuple一样)转换其他值:

bool(42)=>True

注意:尽管[]和""都是假值,但他们本身却不相等([]!="")。对于其他不同类型的假值对象也是如此(如:() != False)

2)条件执行和if语句、else字句、elseif字句、嵌套代码块等

3)更复杂的条件

4)比较运算符

== : 相等运算符

is : 同一性运算符

>>> x=y=[1,2,3]>>> z=[1,2,3]>>> x==y

True>>> x==z

True>>> x isy

True>>> x isz

False

总结:使用==运算符来判断两个对象是否相等,使用is判断两者是否等同(同一个对象)

in:成员资格运算符

字符串和序列比较:字符串可以按照字母顺序排序进行比较

>>> 'alpha'

注:实际的顺序可能会因为使用不同的locale而不同

如果要忽略大小写区别,可以使用upper和lower(一个字母的顺序值可以用ord函数查到,ord与chr函数功能相反)

>>> chr(99)'c'

>>> ord('c')99

其他的序列也可以用同样的方式进行比较,不过比较的不是字符而是元素的其他类型:

>>> [1,2]

True>>> [2,[1,4]]

True

布尔运算符:and、or、not

短路逻辑和条件表达式:布尔运算符有个特征,只有在需要求值时才进行求值。如: x and y 需要两个变量都为真时才为真,如果x为假,表达式立即返回false而不管y的值。这种行为称为短路逻辑(short-circuit logic)或惰性求职(lazy evaluation),主要是避免了无用地执行代码。之类短路逻辑可以用来实现C和java中所谓的三元运算符,Python有一个内置的条件表达式:a if b else c (如果b为真,返回a,否则返回c)

5)断言——assert:如果需要确保程序中的某个条件一定为真才能让程序正常工作的话,assert语句就有用了,它可以在程序中置入检查点。条件后可以添加字符串,用来解释断言:

>>> age = -1

>>> assert 0

File"", line 1, in

assert 0

5:循环

1)while循环——用来在任何条件为真的情况下重复执行一个代码块

>>> x=1

>>> while x<=100:print(x)

x+=1

2)for循环

>>> words=['this','is','an','ex','parrot']>>> for word inwords:print(word)

因为迭代(循环的另外一种说法)某范围的数字是很常见的,所以有某个内建的范围函数供使用:range(),此函数工作方式类似于分片,它包含下限但不包含上限。如果希望下限为0,可以只提供上限:

>>> for number in range(1,101):print(number) #输出1-100数字

xrange函数的循环行为类似于range函数,区别在于range函数一次创建整个序列,而xrange一次只创建一个数(python3.0中range会被转换成xrange风格的函数)。当需要迭代一个巨大的序列时xrange更高效

3)循环遍历字典元素

>>> d={'x':1,'y':2,'z':3}>>> for key ind:print(key, 'corresponds to', d[key])

y corresponds to2x corresponds to1z corresponds to3

注意:字典元素的顺序是没有定义的。换句话说,迭代的时候,字典中的键和值都能保证被处理,但是处理顺序不确定。如果顺序很重要的话,可以将键值保存在单独的列表中

4)一些迭代工具

A:并行迭代——程序可以同时迭代两个序列:

>>> names = ['anne','beth','george','damon']>>> ages = [12,45,32,102]>>> #如果想要打印名字和对应的年龄,可以这样做:

>>> for i inrange(len(names)): #这里i是循环索引的标准变量名print(names[i],'is',ages[i],'years old')

内建的 zip 函数可以用来进行并行迭代,可以把两个序列“压缩”在一起,然后返回一个元组的列表:

>>>list(zip(names,ages))

[('anne', 12), ('beth', 45), ('george', 32), ('damon', 102)]

可以在循环中解包元组:

for name,age inlist(zip(names,ages)):print(name,'is',age,'years old')

zip函数也可以作用于任意多的序列。而且可以应付不等长的序列:当最短的序列“用完”的时候就会停止:

>>> list(zip(range(5),range(1000000)))

[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

B:编号迭代——有时候想要迭代序列中的对象,同时还要获取当前对象的索引:使用内建的 enumerate 函数,这个函数可以在提供索引的地方迭代索引-值对:

View Code

>>> strings = ['abx','aby','abz']>>> for index,string inenumerate(strings):print(index,string)

0 abx1aby2abz>>> for index,string inenumerate(strings):if 'x' instring:print('in')

strings[index]= '[censored]'

in

>>> for index,string inenumerate(strings):print(index,string)

0 [censored]1aby2 abz

C:翻转和排序迭代——reversed 和 sorted 函数。它们同列表的reverse和sort方法类似,但作用于任何序列或可迭代对象上,不是原地修改对象,而是返回翻转或排序后的版本:

>>> sorted([4,3,6,8,3])

[3, 3, 4, 6, 8]>>> sorted('Hello, world!')

[' ', '!', ',', 'H', 'd', 'e', 'l', 'l', 'l', 'o', 'o', 'r', 'w']>>> list(reversed('Hello, world!'))

['!', 'd', 'l', 'r', 'o', 'w', ' ', ',', 'o', 'l', 'l', 'e', 'H']>>> ''.join(reversed('Hello, world!'))'!dlrow ,olleH'

5)跳出循环——break、continue、while True/break习语

whileTrue:

word= input('Please enter a word:')if not word:break

#处理word

print('The word was' + word)

6:列表推导式(list comprehension)——轻量级循环

列表推导式(list comprehension)是利用其他列表创建新列表(类似于数学术语中的集合推导式)的一种方法。它的工作方式类似于for循环:

>>> list(range(10))

[0,1, 2, 3, 4, 5, 6, 7, 8, 9]>>> #列表由range(10)中每个x的平方组成

>>> [x*x for x in range(10)]

[0,1, 4, 9, 16, 25, 36, 49, 64, 81]

如果只想打印出能被3整除的平方数,可以使用模除运算符—— y % 3,当数字可被3整除时返回0。这个语句通过增加一个if部分添加到列表推导式中:

>>> [x*x for x in range(10) if x%3 ==0]

[0,9, 36, 81]

也可以增加更多for

>>> [(x,y) for x in range(3) for y in range(3)]

[(0, 0), (0,1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

作为对比,下面的代码使用两个for创建相同的列表:

>>> result =[]>>> for x in range(3):for y in range(3):

result.append((x,y))>>>result

[(0, 0), (0,1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

也可以和if字句联合使用,像以前一样:

>>> girls = ['alice','bernice','clarice']>>> boys = ['chris','arnold','bob']>>> [b + '+' + g for b in boys for g in girls if b[0] ==g[0]]

['chris+clarice', 'arnold+alice', 'bob+bernice']#得到首字母相同的boys和girls

上面的例子效率不高,因为它会检查每个可能的配对。python有很多解决这个问题的方法:

>>> girls = ['alice','bernice','clarice']>>> boys = ['chris','arnold','bob']>>> letterGirls ={}>>> for girl ingirls:

letterGirls.setdefault(girl[0],[]).append(girl)>>> print(list(b+'+'+g for b in boys for g inletterGirls[b[0]]))

['chris+clarice', 'arnold+alice', 'bob+bernice']

这个程序建造了一个letterGirls的字典,其中每一项都把单字母作为键,以女孩名字组成的列表作为值。字典建立后,列表推导式循环整个男孩集合,并且查找那些和当前男孩名字首字母相同的女孩集合。这样列表推导式就不用尝试所有的男孩女孩组合,检查首字母是否匹配

>>> girls = ['alice','bernice','clarice']>>> boys = ['chris','arnold','bob']>>> letterGirls={}>>> for girl ingirls:

letterGirls.setdefault(girl[0],[]).append(girl)print(letterGirls)

{'a': ['alice']}

{'a': ['alice'], 'b': ['bernice']}

{'a': ['alice'], 'b': ['bernice'], 'c': ['clarice']}

7:补充

1)pass 语句 —— 在代码中做占位符使用。

if(name=="Aaron"):print('Welcome')elif(name=='Enid'):#To-Do:

pass

elif(name=='Bill Gates'):print('Access Denied')

2)使用 del 删除——一般来说,Python会删除那些不再使用的对象(垃圾收集)

View Code

>>> scoundrel = {'age':42,'firstname':'Robin','lastname':'of Locksley'}>>> robin =scoundrel>>>scoundrel

{'firstname': 'Robin', 'lastname': 'of Locksley', 'age': 42}>>>robin

{'firstname': 'Robin', 'lastname': 'of Locksley', 'age': 42}>>> scoundrel=None>>>robin

{'firstname': 'Robin', 'lastname': 'of Locksley', 'age': 42}>>> print(scoundrel)

None

robin和scoundrel都被绑定到同一个字典上。所以当设置scoundrel为None的时候,字典通过robin还是可用的。但是当把robin也设置为None的时候,字典就“漂”在内存里面了,没有任何名字绑定到它上面。Python解释器将会删除这个字典(垃圾收集)。注意也可以使用None之外的其他值,字典同样会“消失”

另外一个方法是使用 del 语句,它不仅会移除一个对象的引用,还会移除那个名字本身

>>> x=1

>>> delx>>>x

Traceback (most recent call last):

File"", line 1, in x

NameError: name'x' is not defined

>>> x=["Hello","world"]>>> y=x>>> y[1]="Python"

>>>x

['Hello', 'Python']>>> delx>>>y

['Hello', 'Python']

删除x之后,y并没有随之消失,为什么?x和y都指向同一个列表,但删除x并不会影响y。原因是删除的只是名称,而不是列表本身(值)。事实上,在Python中是没有办法删除值的(也不需要考虑删除值,因为值不再使用的时候,python解释器会负责内存的回收)

3)使用 exec 和 eval 执行和求值字符串——有些时候可能会需要动态的创造Python代码,然后将其作为语句执行或作为表达式计算。慎用。

A:exec

>>> exec ("print('Hello,world')")

Hello,world

为安全起见,可以增加命名空间(命名空间,或称为域:scope),可以通过增加 in 来实现,其中的就是起到放置代码字符串命名空间作用的字典:

>>> from math importsqrt>>> scope={}>>> exec('sqrt = 1', scope)>>> sqrt(4)2.0

>>> scope['sqrt']1

B:eval(用于“求值”)——exec语句会执行一系列python语句,而eval 会计算python表达式(以字符串形式书写),并返回结果值,(exec语句并不返回任何对象,因为它本身就是语句)。例如,可以使用下面的代码创建一个python计算器:

>>> eval(input("Enter an arithmetic expression:")) #evale(input(...))实际等同于input(...)

Enter an arithmetic expression:6+18*2

42

与exec一样,eval也可以使用命名空间(可以给eval提供两个命名空间,一个全局的一个局部的。全局的必须是字典,局部的可是任何形式的映射)

#给exec或eval语句提供命名空间时,还可以在真正使用命名空间前放置一些值进去

>>> scope ={}>>> scope['x']=2

>>> scope['y']=3

>>> eval('x*y',scope)6

#同理,exec或eval调用的作用域也能在另外一个上面使用:

>>> scope ={}>>> exec('x=2',scope)>>> eval('x*x',scope)4

小结:

打印:print语句可以打印由逗号隔开的多个值,如果语句以逗号结尾,后面的print语句会在同一行内继续打印

导入:可以使用import...as...语句进行函数的局部重命名

赋值:通过序列解包和链式赋值功能,多个变量赋值可以一次性赋值,通过增量赋值可以原地改变变量

块:块是通过缩排使语句成组的一个种方法

条件:几个条件串联使用:if/elif/else。还有一种变体叫条件表达式:a if b else c

断言:简单的说就是肯定某事(布尔表达式)为真。如果表达式为假,断言会让程序产生异常。比起让错误潜藏在程序中,更好的方法是尽早找到错误

循环:continue、break

列表推导式:它不是真正的语句——是看起来像循环的表达式。通过它可以从旧列表中产生新的列表

pass、del、exec、eval 等语句:pass语句什么都不做,做占位符使用。del用来删除变量或数据结构的一部分,但不能用来删除值。exec语句用与执行python程序相同的方式来执行字符串。内建的eval函数对写在字符串中的表达式进行计算并返回结果

第六章:抽象

1:懒惰即美德

斐波那契数列(任一个数是前两个数之和的数字序列):

>>> fibs = [0,1]>>> for i in range(8):

fibs.append(fibs[-2] + fibs[-1])>>>fibs

[0,1, 1, 2, 3, 5, 8, 13, 21, 34]

fibs=[0,1]

num=input('How many Fibonacci numbers do you want?')for i in range(int(num)-2):

fibs.append(fibs[-2]+fibs[-1])print(fibs)

2:抽象和结构——抽象可以节省很多工作,它是使计算机程序可以让人读懂的关键(这也是最基本的要求,不管是读还是写程序)

3:创建函数——函数是可以调用(可以包含参数,放在圆括号中),它执行某种行为并且返回一个值。内建的callable函数可以用来判断函数是否可调用:

>>> importmath>>> x=1

>>> y=math.sqrt>>>callable(x)

False>>>callable(y)

True

使用 def 语句定义函数:

>>> defhello(name):return 'Hello,' + name + '!'

>>> print(hello('d'))

Hello, d!

>>> deffibs(num):

result= [0,1]for i in range(int(num)-2):

result.append(result[-2]+result[-1])returnresult>>> fibs(10)

[0,1, 1, 2, 3, 5, 8, 13, 21, 34]

1)记录函数——想要给函数写文档,可以加入注释(以#开头)。另外一个方式就是直接写上字符串,这类字符串在其他地方可能会非常有用,比如在def语句后面(以及在模块或者类的开头)。如果在函数的开头写下字符串,它就会作为函数的一部分进行存储,这称为文档字符串

>>> defsquare(x):'Calculates the square of the number x.'

return x*x>>> square.__doc__

'Calculates the square of the number x.'

内建函数 help 非常有用。在交互式解释器中使用它,可以得到关于函数,包括它的文档字符串的消息:

>>>help(square)

Help on function squarein module __main__:

square(x)

Calculates the square of the number x.

2)并非真正函数的函数——数学意义的函数,总会返回点什么。python有些函数却并不返回任何东西,在其他语言中,这类函数可能有其他名字(如过程),但python的函数就是函数,即便它从学术上讲不是函数。

没有return语句,或者虽有return语句但return后边没有跟任何值的函数不返回值。

4:参数魔法

1)值从哪里来——函数被定义后,所操作的值是从哪里来的呢?写在def语句中函数名后面的变量通常称为函数的形式参数,而调用函数的时候提供的值是实际参数,或者称为参数。

2)改变参数——在函数内为参数赋值不会改变任何外部变量的值(这是因为:参数存储在局部作用域(local scope)内)

字符串(以及数字和元组)是不可变的,即无法被修改(也就是说只能用新的值覆盖)。所以它们做参数时无需多介绍:

>>> deftry_to_change(n):

n='Mr.Gumby'

>>> name = 'Mrs.Entity'

>>>try_to_change(name)>>>name'Mrs.Entity'

但是如果将可变的数据结构如列表用作参数会发生什么:

>>> defchange(n):

n[0]='Mr.Gumby'

>>> names=['Mrs.Entity','Mrs.Thing']>>>change(names)>>>names

['Mr.Gumby', 'Mrs.Thing']

参数names发生了改变。如果要避免这种情况,可以复制一个列表的副本。在序列中做切片的时候,返回的切片总是一个副本。因此,如果你复制了整个列表的切片,将会得到一个副本,改变副本将不会影响到原始的列表

在某些语言(如C++、Pascal和Ada)中,重绑定参数并且使这些改变影响到函数外的变量是很平常的事情,但是在python中这是不可能的:函数只能修改参数对象本身。如果参数是不可变的,如数字,则没有办法。此时应该从函数中返回所有你需要的值(如果值多于一个的话就以元组形式返回):

>>> def inc(x):return x+1

>>> foo=10

>>> foo=inc(foo)>>>foo11

#如果真想改变参数,可以使用一点小技巧,即将值放置在列表中:

>>> def inc(x):x[0]=x[0]+1

>>> foo=[10]>>>inc(foo)>>>foo

[11]

3)关键字参数和默认值——参数很多的时候,参数的顺序很难记住,可以提供参数的名字(参数名和值一定要对应),使用参数名提供的参数叫关键字参数(上面例子的参数是位置参数):

>>> defhello(greeting,name):print('%s, %s!' %(greeting,name))>>> hello(greeting='Hello',name='World')

Hello, World!

关键字参数还可以给函数提供默认值:

>>> def hello(greeting='Hello',name='World'):print('%s, %s!' %(greeting,name))>>>hello()

Hello, World!

4)收集参数:用户可以给函数提供任意多的参数

>>> def print_params(*params):print(params)>>> print_params('test')

('test',)>>> print_params('test','test2')

('test', 'test2')>>> print_params(1,2,3)

(1, 2, 3)

星号的作用就是”收集其余的位置参数“,如果不提供任何收集的元素,params就是个空元组

>>> 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_params_3(**params):print(params)>>> print_params_3(x=1,y=2,z=3)

{'z': 3, 'x': 1, 'y': 2}

返回的是字典而不是元组。放一起用看看:

>>> def print_params_4(x,y,z=3,*pospar,**keypar):print(x,y,z)print(pospar)print(keypar)>>> print_params_4(1,2,3,5,6,7,foo=1,bar=2)1 2 3(5, 6, 7)

{'foo': 1, 'bar': 2}>>> print_params_4(1,2)1 2 3()

{}

5)反转过程——函数收集的逆过程:在调用而不是在定义时使用

>>> def add(x,y):return x+y>>> params=(1,2)>>> add(*params)3

可以使用同样的技术来处理字典——使用双星号运算符:

>>> defhello(greeting,name):print(greeting,name)>>> params={'name':'Sir Robin','greeting':'Well met'}>>> hello(**params)

Well met Sir Robin

>>> def with_stars(**kwds):print(kwds['name'],'is',kwds['age'],'years old')>>> defwithout_stars(kwds):print(kwds['name'],'is',kwds['age'],'years old')>>> args={'name':'Mr.Gumby','age':42}>>> with_stars(**args)

Mr.Gumbyis 42years old>>>without_stars(args)

Mr.Gumbyis 42 years old

提示:使用拼接(Splicing)操作符”传递“参数很有用,因为这样就不用关心参数的个数之类的问题(在调用超类的构造函数时这个方法尤其有用):

>>> def foo(x,y,z,m=0,n=0):print(x,y,z,m,n)>>> def call_foo(*args,**kwds):print("Calling foo!")

foo(*args,**kwds)>>> call_foo(1,2,3)

Calling foo!1 2 3 0 0

6)练习使用参数:

View Code

def story(**kwds):return 'Once upon a time, there was a'\'%(job)s called %(name)s.' %kwdsdef power(x,y,*others):ifothers:print('Received redundant parameters:',others)returnpow(x,y)def interval(start,stop=None,step=1):'Imitates range() for step > 0'

if stop is None: #如果没有为stop提供值...

start,stop=0,start #指定参数

result =[]

i=startwhile i

result.append(i)

i+=stepreturnresult#samples:

>>> print(story(job='king',name='Gumby'))

Once upon a time, there was a king called Gumby.>>> params = {'job':'language','name':'Python'}>>> print(story(**params))

Once upon a time, there was a language called Python.>>> del params['job']>>> print(story(job='stroke of genius',**params))

Once upon a time, there was a stroke of genius called Python.>>> power(2,3)8

>>> power(3,2)9

>>> power(y=3,x=2)8

>>> params=(5,)*2

>>> power(*params)3125

>>> power(3,3,'Hello,world')

Received redundant parameters: ('Hello,world',)27

>>> interval(10)

[0,1, 2, 3, 4, 5, 6, 7, 8, 9]>>> interval(1,5)

[1, 2, 3, 4]>>> interval(3,12,4)

[3, 7, 11]>>> power(*interval(3,7))

Received redundant parameters: (5, 6)81

5:作用域——变量可以看做是值的名字。在执行x=1的赋值语句后,名称x引用值1。这就像用字典一样,键引用值,当然变量所对应的值用的是个”不可见“的字典。内建的 vars 函数可以返回这个字典:

View Code

>>> x=1

>>> scope=vars()>>> scope['x']1

>>> scope['x'] + 1

2

>>>x1

>>> scope['x'] += 1

>>>x2

警告:一般来说,vars所返回的字典是不能修改的,结果是未定义的,可能得不到想要的结果

这类”不可见字典“叫做 命名空间 或者 作用域 。除了全局作用域外,每个函数调用都会创建一个新的作用域:

>>> def foo():x=42

>>> x=1

>>>foo()>>>x1

这里的foo函数改变(重绑定)了变量x,但是在最后的时候,x并没有变。这是因为当调用foo的时候,新的命名空间就被创建了,它作用于foo内的代码块。赋值语句x=42只在内部作用域(局部命名空间)起作用,所以它不影响外部(全局)作用域中的x。函数内的变量:局部变量(local variable)。参数的工作原理类似局部变量,所以用全局变量的名字作为参数并没有问题

如果局部变量和全局变量相同的话,全局变量会被局部变量屏蔽。如果需要的话,可以使用globals 函数获取全局变量值,该函数的近亲的vars,它可返回全局变量的字典(locals返回局部变量的字典)

>>> defcombine(parameter):print(parameter + globals()['parameter'])>>> parameter = 'berry'

>>> combine('Shrub')

Shrubberry

重绑定全局变量(使变量引用其他新值)。如果在函数内部将值赋予一个变量,它会自动成为局部变量——除非告知python将其声明为全局变量:

>>> x=1

>>> defchange_global():globalx

x=x+1

>>>change_global()>>>x2

嵌套作用域:python的函数是可以嵌套的,主要应用:需要一个函数”创建“另外一个。一个函数位于另外一个函数里面,外层函数返回里层函数。也就是说函数本身被返回了——但并没有被调用。重要的是返回的函数还可以访问它的定义所在的作用域。即,它”带着“它的环境(和相关的局部变量):

>>> defmultiplier(factor):defmultiplyByFactor(number):return number *factorreturn multiplyByFactor

每次调用外层函数,它内部的函数都被重新绑定,factor变量每次都有一个新的值。由于Python的嵌套作用域,来自(multiplier的)外部作用域的这个变量,稍后会被内层函数访问。

>>> multiplier(2).multiplyByFactor at 0x02E21A50>

>>> double = multiplier(2)>>> double(5)10

>>> triple=multiplier(3)>>> triple(3)9

>>> multiplier(5)(4)20

类似multiplyByFactor 函数存储子封闭作用域的行为叫做:闭包(closure)

外部作用域的变量一般是不能进行重新绑定的,但在Python3.0中,nonlocal关键字被引入。它和global关键字的使用方式类似,可以让用户对外部作用域(但并非全局作用域)的变量进行赋值

6:递归——简单来说就是引用(或调用)自身的意思

defrecursion():return recursion()

上面是个无穷递归(infinite recursion),类似于以while True开始的无穷循环。理论上讲它会永远运行下去。然而每次调用函数都会用掉一点内存,在足够的函数调用发生后,空间就不够了,程序会以一个”超过最大递归深度“的错误结束

有用的递归包含以下几部分:

当函数直接返回值时有基本实例(最小可能性问题)

递归实例,包括一个或多个问题最小部分的递归调用

这里的关键就是将问题分解为小部分,递归不能永远继续下去,因为它总是以最小可能性问题结束,而这些问题又存储在基本实例中。

每次函数被调用时,针对这个调用的新命名空间会被创建,意味着当函数调用”自身“时,实际上运行的是两个不同的函数(或者说同一个函数具有两个不同的命名空间)

1)两个经典递归:阶乘和幂

计算数n的阶乘,n的阶乘的定义为:n*(n-1)*(n-2)*...*1

#使用循环实现

deffactorial(n):

result=nfor i in range(1,n):

result*=ireturn result

这个方法过程主要是:首先将result赋到n上,然后result依次与1~n-1 的数相乘,最后返回结果。

递归实现。关键在于阶乘的数学定义:

1的阶乘是1;

大于1的数n的阶乘是n乘n-1的阶乘

deffactorial(n):if n==1:return 1

else:return n * factorial(n-1)

计算幂,就像内建pow函数和**运算符一样

defpower(x,n):

result= 1

for i inrange(n):

result*=xreturn result

改编成递归版本:

对于任意数字x来说,power(x,0)是1;

对于任何大于0的数字来说,power(x,n)是x乘以(x,n-1)的结果

defpower(x,n):if n ==0:return 1

else:return x * power(x,n-1)

提示:如果函数或算法很复杂而且难懂的话,在实现之前用自己的话明确定义一下是很有帮助的。这类程序称为:伪代码

递归可以用循环代替,大多数情况还更有效率,但是递归更加易读

2)另外一个经典:二元查找(binary search)

假如猜一个1~100的数字,真正只需要猜7次:第一个问题类似”数字是否大于50?“,如果数字大于50,则问”是否大于75?“,然后继续将满足条件的值=等分(排除不满足条件的),直到直到正确答案。

一个普遍的问题就是查找一个数字是否存在于一个(排过序)的序列中,还要找到具体位置。还可以使用同样的过程。”这个数字是否在序列正中间右边?“,如果不是,”那么是否在第二个1/4范围内(左侧靠右)?“,然后继续这样下去。这个算法的本身就是递归的定义,让我们重看定义:

如果上下限相同,那么就是数字所在的位置,返回;

否则找到两者的中点(上下限的平均值),查找数字是在左侧还是右侧,继续查找数字所在的那半部分。

这个递归例子的关键就是顺序,只需要比较中间元素和所查找的数字,如果查找数字较大,则该数字就一定在右侧

View Code

def search(sequence,number,lower=0,upper=None):if upper is None : upper = len(sequence)-1

if lower ==upper:assert number ==sequence[upper]returnupperelse:

middle= (lower + upper) // 2

if number >sequence[middle]:return search(sequence,number,middle+1,upper)else:returnsearch(sequence,number,lower,middle)#test

>>> seq = [34,67,8,123,4,100,95]>>>seq.sort()>>>seq

[4, 8, 34, 67, 95, 100, 123]>>> search(seq,34)2

使用index也可以查找,但是index是循环,效率有低。查找100内的一个数(或位置),只需要7个问题,用循环的话,最糟糕的情况要问100个问题

另外:标准库中的 bisect 模块可以非常有效地实现二元查找

Python在应对”函数式编程“方面有一些有用的函数:map、filter 和 reduce 函数(python3.0中这些被移到 functools 模块中)

使用map函数可以将序列中的元素全部传递给一个函数:

>>> list(map(str,range(10))) #Equivalent to [str(i) for i in range(10)]

['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

filter 函数可以基于一个返回布尔值的函数对元素进行过滤

>>> deffunc(x):returnx.isalnum()>>> seq = ['foo','x41','?!','***']>>>list(filter(func,seq))

['foo', 'x41']#使用列表推导式可以不用专门定义一个函数:

>>> [x for x in seq ifx.isalnum()]

['foo', 'x41']#事实上,还有个叫lambda表达式的特性,可以创建短小的函数#lambda在数学中表示匿名函数

>>> list(filter(lambdax:x.isalnum(),seq))

['foo', 'x41']

列表推导式更易读。reduce 函数一般来说不能轻松被列表推导式替代,但是通常用不到这个功能。

它会将序列的两个元素与给定的函数联合使用,并且将它们的返回值和第3个元素继续联合使用,直到整个序列处理完毕,并且得到一个最终结果。

例如,计算一个序列的数字的和(也可以使用内建函数 sum:使用operator模块中的函数更高效):

>>> numbers = [72,101,108,108,111,44]>>> reduce(lambda x,y:x+y,numbers)

小结:

抽象:抽象是隐藏多余细节的艺术。定义处理细节的函数可以让程序更抽象

函数定义:def语句

参数:位置参数和关键字参数。

作用域:变量存储在作用域(命名空间)中。python中两类主要的作用域:全局作用域和局部作用域。作用域可以嵌套

递归:函数可以调用自身——一切递归实现的功能都可以用循环实现,但是有时候递归函数更易读

函数型编程:python有一些进行函数型编程的机制。包括lambda表达式以及map、filter和reduce函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值