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字句,在该字句后给出名字,或为整个模块提供别名

>>> import math 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
>>> x
1

当函数或者方法返回元组(或者其他序列或可迭代对象)时,这个特性尤其有用。假设需要获取(和删除)字典中任意的键值对,可以使用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
>>> x
6

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

>>> 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 is y
True
>>> x is z
False

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

in:成员资格运算符

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

>>> 'alpha'<'beta'
True

注:实际的顺序可能会因为使用不同的locale而不同
如果要忽略大小写区别,可以使用upper和lower(一个字母的顺序值可以用ord函数查到,ord与chr函数功能相反)

>>> chr(99)
'c'
>>> ord('c')
99

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

>>> [1,2]<[2,1]
True
>>> [2,[1,4]]<[2,[1,5]]
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<age<100, 'The age must be realistic'
Traceback (most recent call last):
  File "<pyshell#103>", line 1, in <module>
    assert 0<age<100, 'The age must be realistic'
AssertionError: The age must be realistic

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

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

2)for循环

>>> words=['this','is','an','ex','parrot']
>>> for word in words:
    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 in d:
    print(key, 'corresponds to', d[key])

y corresponds to 2
x corresponds to 1
z corresponds to 3

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

4)一些迭代工具
A:并行迭代——程序可以同时迭代两个序列:

>>> names = ['anne','beth','george','damon']
>>> ages = [12,45,32,102]
>>> #如果想要打印名字和对应的年龄,可以这样做:
>>> for i in range(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 in list(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 in enumerate(strings):
    print(index,string)

0 abx
1 aby
2 abz
>>> for index,string in enumerate(strings):
    if 'x' in string:
        print('in')
        strings[index] = '[censored]'

    
in
>>> for index,string in enumerate(strings):
    print(index,string)

0 [censored]
1 aby
2 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习语

while True:
    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 in girls:
    letterGirls.setdefault(girl[0],[]).append(girl)
>>> print(list(b+'+'+g for b in boys for g in letterGirls[b[0]]))
['chris+clarice', 'arnold+alice', 'bob+bernice']

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

>>> girls = ['alice','bernice','clarice']
>>> boys = ['chris','arnold','bob']
>>> letterGirls={}
>>> for girl in girls:
    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
>>> del x
>>> x
Traceback (most recent call last):
  File "<pyshell#96>", line 1, in <module>
    x
NameError: name 'x' is not defined
>>> x=["Hello","world"]
>>> y=x
>>> y[1]="Python"
>>> x
['Hello', 'Python']
>>> del x
>>> 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<scope> 来实现,其中的<scope>就是起到放置代码字符串命名空间作用的字典:

>>> from math import sqrt
>>> 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函数可以用来判断函数是否可调用:

>>> import math
>>> x=1
>>> y=math.sqrt
>>> callable(x)
False
>>> callable(y)
True

使用 def 语句定义函数:

>>> def hello(name):
    return 'Hello, ' + name + '!'

>>> print(hello('d'))
Hello, d!
>>> def fibs(num):
    result = [0,1]
    for i in range(int(num)-2):
        result.append(result[-2]+result[-1])
    return result

>>> fibs(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

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

>>> def square(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 square in module __main__:

square(x)
    Calculates the square of the number x.

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

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

2)改变参数——在函数内为参数赋值不会改变任何外部变量的值(这是因为:参数存储在局部作用域(local scope)内)
字符串(以及数字和元组)是不可变的,即无法被修改(也就是说只能用新的值覆盖)。所以它们做参数时无需多介绍:

>>> def try_to_change(n):
    n='Mr.Gumby'

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

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

>>> def change(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)
>>> foo
11
#如果真想改变参数,可以使用一点小技巧,即将值放置在列表中:
>>> def inc(x):x[0]=x[0]+1

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

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

>>> def hello(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

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

>>> def hello(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')

>>> def without_stars(kwds):
    print(kwds['name'],'is',kwds['age'],'years old')

>>> args={'name':'Mr.Gumby','age':42}
>>> with_stars(**args)
Mr.Gumby is 42 years old
>>> without_stars(args)
Mr.Gumby is 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.' % 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:    #如果没有为stop提供值...
        start,stop=0,start #指定参数
    result = []

    i=start
    while i < stop :
        result.append(i)
        i += step
    return result

#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
>>> x
1
>>> scope['x'] += 1
>>> x
2

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

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

>>> def foo():x=42
>>> x=1
>>> foo()
>>> x
1

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

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

>>> def combine(parameter):
    print(parameter + globals()['parameter'])

>>> parameter = 'berry'
>>> combine('Shrub')
Shrubberry

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

>>> x=1
>>> def change_global():
    global x
    x=x+1

>>> change_global()
>>> x
2

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

>>> def multiplier(factor):
    def multiplyByFactor(number):
        return number * factor
    return multiplyByFactor

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

>>> multiplier(2)
<function multiplier.<locals>.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:递归——简单来说就是引用(或调用)自身的意思

def recursion():
    return recursion()

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

有用的递归包含以下几部分:
当函数直接返回值时有基本实例(最小可能性问题)
递归实例,包括一个或多个问题最小部分的递归调用

这里的关键就是将问题分解为小部分,递归不能永远继续下去,因为它总是以最小可能性问题结束,而这些问题又存储在基本实例中。
每次函数被调用时,针对这个调用的新命名空间会被创建,意味着当函数调用”自身“时,实际上运行的是两个不同的函数(或者说同一个函数具有两个不同的命名空间)

1)两个经典递归:阶乘和幂
计算数n的阶乘,n的阶乘的定义为:n*(n-1)*(n-2)*...*1

#使用循环实现
def factorial(n):
    result = n
    for i in range(1,n):
        result *= i
    return result

这个方法过程主要是:首先将result赋到n上,然后result依次与1~n-1 的数相乘,最后返回结果。
递归实现。关键在于阶乘的数学定义:
1的阶乘是1;
大于1的数n的阶乘是n乘n-1的阶乘

def factorial(n):
    if n==1:
        return 1
    else:
        return n * factorial(n-1)

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

def power(x,n):
    result = 1
    for i in range(n):
        result *= x
    return result

改编成递归版本:
对于任意数字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)

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

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

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

#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 函数可以基于一个返回布尔值的函数对元素进行过滤

>>> def func(x):
    return x.isalnum()
>>> seq = ['foo','x41','?!','***']
>>> list(filter(func,seq))
['foo', 'x41']
#使用列表推导式可以不用专门定义一个函数:
>>> [x for x in seq if x.isalnum()]
['foo', 'x41']
#事实上,还有个叫lambda表达式的特性,可以创建短小的函数
#lambda在数学中表示匿名函数
>>> list(filter(lambda x: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函数


原文地址:https://www.cnblogs.com/mumue/p/2860226.html