十一:函数和函数式编程
11.1 什么是函数?
函数是对程序逻辑进行结构化或过程化的一种编程方法。能将整块代码巧妙地隔离成易于管理
的小块,把重复代码放到函数中而不是进行大量的拷贝--这样既能节省空间,也有助于保持一致性,因为你只需改变单个的拷贝而无须去寻找再修改大量复制代码的拷贝
11.2 调用函数
11.2.1.函数操作符
11.2.2关键字参数
调用者通过函数调用中的参数名字来区
分参数。这样规范允许参数缺失或者不按顺序,因为解释器能通过给出的关键字来匹配参数的值。
def foo(x):
foo_suite # presumably does some processing with 'x'
标准调用 foo():foo(42) foo('bar') foo(y)
关键字调用 foo():foo(x=42)foo(x='bar') foo(x=y)
11.2.3.默认参数
默认参数就是声明了默认值的参数。因为给参数赋予了默认值,所以, 在函数调用时,不向该
参数传入值也是允许的
11.3.4.函数属性
11.3.6 *函数(与方法)装饰器
from time import ctime, sleep
def tsfunc(func):
def wrappedFunc():
print '[%s] %s() called' % (
ctime(), func.__name__)
return func()
return wrappedFunc
def demo():
print 'demo'
# 实际上装饰器调用类似于下面的调用
foo = tsfunc(demo)
@tsfunc
def foo():
pass
#使用带装饰器的方法
foo()
sleep(4)
for i in range(2):
sleep(1)
foo()
11.7 函数式编程
内建函数 apply()、filter()、map()、reduce()
内建函数 描述
apply(func[, nkw][, kw]) a 用可选的参数来调用 func,nkw 为非关键字参数,kw 关
键字参数;返回值是函数调用的返回值。
filter(func, seq)b 调用一个布尔函数 func 来迭代遍历每个 seq 中的元素; 返回一个
使 func 返回值为 ture 的元素的序列。
map(func, seq1[,seq2...])b 将函数 func 作用于给定序列(s)的每个元素,并用一个列表来提
供返回值;如果 func 为 None, func 表现为一个身份函数,返回
一个含有每个序列中元素集合的 n 个元组的列表。
reduce(func, seq[, init]) 将二元函数作用于 seq 序列的元素,每次携带一对(先前的结果
以及下一个序列元素),连续的将现有的结果和下雨给值作用在获
得的随后的结果上,最后减少我们的序列为一个单一的返回值;如
果初始值 init 给定,第一个比较会是 init 和第一个序列元素而不
是序列的头两个元素。
使用举例:
add = lambda x,y : x + y
print add(1,2)
sequence = [1,2,3,4,5,6,7,8,9,10]
fun = lambda x : x % 2 == 0
seq = filter(fun,sequence)
print seq
def filter(fun,seq):
filter_seq = []
for item in seq:
if fun(item):
filter_seq.append(item)
return filter_seq
11.8 变量作用域
11.8.1 全局变量与局部变量
核心笔记:搜索标识符(aka 变量,名字,等等)
当搜索一个标识符的时候,python 先从局部作用域开始搜索。如果在局部作用域内没有找到那
个名字,那么就一定会在全局域找到这个变量否则就会被抛出 NameError 异常。
一个变量的作用域和它寄住的名字空间相关。我们会在 12 章正式介绍名字空间;对于现在只能
说子空间仅仅是将名字映射到对象的命名领域,现在使用的变量名字虚拟集合。作用域的概念和用
于找到变量的名字空间搜索顺序相关。当一个函数执行的时候,所有在局部命名空间的名字都在局
部作用域内。那就是当查找一个变量的时候,第一个被搜索的名字空间。如果没有在那找到变量的
话,那么就可能找到同名的全局变量。这些变量存储(搜索)在一个全局以及内建的名字空间,。
仅仅通过创建一个局部变量来“ 隐藏“或者覆盖一个全局变量是有可能的。回想一下,局部名
字空间是首先被搜索的,存在于其局部作用域。如果找到一个名字,搜索就不会继续去寻找一个全
局域的变量,所以在全局或者内建的名字空间内,可以覆盖任何匹配的名字
11.8.4 闭包
如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的
变量进行引用,那么内部函数就被认为是 closure。定义在外部函数内的但由内部函数引用或者使用
的变量被称为自由变量
11.8.6 变量作用域和名字空间
11.10 生成器
协程是可以运行的独立函数调
用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。在有调用者和(被调用的)协同
程序也有通信。举例来说,当协同程序暂停的时候,我们能从其中获得一个中间的返回值,当调用
回到程序中时,能够传入额外或者改变了的参数,但仍能够从我们上次离开的地方继续,并且所有
状态完整。挂起返回出中间值并多次继续的协同程序被称为生成器,那就是 python 的生成器真正在
做的事。
==什么是python 式的生成器?==
从句法上讲,生成器是一个带 yield 语句的函数。一个函数或者子
程序只返回一次,但一个生成器能暂停执行并返回一个中间的结果----那就是 yield 语句的功能, 返
回一个值给调用者并暂停执行。当生成器的 next()方法被调用的时候,它会准确地从离开地方继续
(当它返回[一个值以及]控制给调用者时)
==何时使用生成器==
使用生成器最好的地方就是当你正迭代穿越一个巨大的数据集合,而重复迭代这个数据集合是
一个很麻烦的事,比如一个巨大的磁盘文件,或者一个复杂的数据库查询。对于每行的数据,你希
望执行非元素的操作以及处理,但当正指向和迭代过它的时候,你“不想失去你的地盘“。
11.10.2 加强的生成器特性
十二:模块
12.1 什么是模块
一个文件被看作是一个独立模块, 一个模块也可以被看作是一个文件,模块的文件名就是模
块的名字加上扩展名 .py 。与其它可以导入类
(class)的语言不同,在 Python 中你导入的是模块或模块属性
12.3名称空间
在执行期间有两个或三个活动的名称空间。 这==三个名称空间分别是
局部名称空间, 全局名称空间和内建名称空间,== 但局部名称空间在执行期间是不断变化的, 所以我
们说"两个或三个"。 从名称空间中访问这些名字依赖于它们的加载顺序, 或是系统加载这些名称空间的顺序。
==Python 解释器首先加载内建名称空间。 它由 builtins 模块中的名字构成。 随后加载执
行模块的全局名称空间, 它会在模块开始执行后变为活动名称空间。 这样我们就有了两个活动的名称空间==
如果在执行期间调用了一个函数, 那么将创建出第三个名称空间, 即局部名称空间。 我们可以
通过 globals() 和 locals() 内建函数判断出某一名字属于哪个名称空
核心笔记: builtins 和 builtin
builtins 模块和 builtin 模块不能混淆。 虽然它们的名字相似——尤其对于新手来
说。 builtins 模块包含内建名称空间中内建名字的集合。 其中大多数(如果不是全部的话)来
自 builtin 模块, 该模块包含内建函数, 异常以及其他属性。 在标准 Python 执行环境下,
builtins 包含 builtin 的所有名字。 Python 曾经有一个限制执行模式, 允许你修改
builtins , 只保留来自 builtin 的一部分, 创建一个沙盒(sandbox)环境。但是, 因为
它有一定的安全缺陷, 而且修复它很困难, Python 已经不再支持限制执行模式。
如果在执行期间调用了一个函数, 那么将创建出第三个名称空间, 即局部名称空间。 我们可以
通过 globals() 和 locals() 内建函数判断出某一名字属于哪个名称空间。
12.3.1 名称空间与变量作用域比较
名称空间是纯粹意义上的名字和对象间的映射关系, 而作用域还指出了从用户代码的哪些物
理位置可以访问到这些名字
== 那么确定作用域的规则是如何联系到名称空间的呢? 它所要做的就是名称查询. 访问一个属性
时, 解释器必须在三个名称空间中的一个找到它。 首先从局部名称空间开始, 如果没有找到, 解释
器将继续查找全局名称空间 ==
class MyUltimatePythonStorageDevice(object):
pass
bag = MyUltimatePythonStorageDevice()
bag.x = 100 #动态加载属性
bag.y = 200
bag.version = 0.1
bag.completed = False
你可以把任何想要的东西放入一个名称空间里。 像这样使用一个类(实例)是很好的, 你甚至
不需要知道一些关于 OOP 的知识(注解: 类似这样的变量叫做实例属性。) 不管名字如何, 这个实
例只是被用做一个名称空间
12.4 导入模块
12.5 模块导入的特性
12.5.1 载入时执行模块
==加载模块会导致这个模块被"执行"。 也就是被导入模块的顶层代码将直接被执行。 这通常包
括设定全局变量以及类和函数的声明。 如果有检查 name 的操作, 那么它也会被执行。==
当然, 这样的执行可能不是我们想要的结果。 你应该把尽可能多的代码封装到函数。 明确地说,
只把函数和模块定义放入模块的顶层是良好的模块编程习惯
12.5.2 导入(import )和加载(load)
==一个模块只被加载一次, 无论它被导入多少次。 这可以阻止多重导入时代码被多次执行。 例
如你的模块导入了 sys 模块, 而你要导入的其他 5 个模块也导入了它, 那么每次都加载 sys (或
是其他模块)不是明智之举! 所以, 加载只在第一次导入时发生。==
12.5.3 导入到当前名称空间的名称
12.5.3 导入到当前名称空间的名称
12.6 模块内建函数
12.6.1 import()
这意味着 import 语句调用 import() 函数完成它的工作。提供这个函数是为了让有特殊需要的用户覆盖它, 实现
自定义的导入算法
12.6.2 globals() 和 locals()
globals() 和 locals() 内建函数分别返回调用者全局和局部名称空间的字典。 在一个函数内
部, 局部名称空间代表在函数执行时候定义的所有名字, locals() 函数返回的就是包含这些名字
的字典。 globals() 会返回函数可访问的全局名字。
在全局名称空间下, globals() 和 locals() 返回相同的字典, 因为这时的局部名称空间就是
全局空间。
12.7 包
包是一个有层次的文件目录结构, 它定义了一个由模块和子包组成的 Python 应用程序执行
环境。Python 1.5 加入了包, 用来帮助解决如下问题:
- 为平坦的名称空间加入有层次的组织结构
- 允许程序员把有联系的模块组合到一起
- 允许分发者使用目录结构而不是一大堆混乱的文件
- 帮助解决有冲突的模块名称
与类和模块相同, 包也使用句点属性标识来访问他们的元素。 使用标准的 import 和
from-import 语句导入包中的模块。
12.7.1 目录结构
_init_.py 文件。 这些是初始化模块,
from-import 语句导入子包时需要用到它。 如果没有用到, 他们可以是空文件。
12.7.2 使用 from-import 导入包
包同样支持 from-import all 语句:
from package.module import *
这样的语句会导入哪些文件取决于操作系统的文件系统. 所以我们在init.py 中加入 all 变量. 该变量包含执行这样的语句时应该导入的模块的名字. 它由一个模块名字符串列表组成
2.7.3 绝对导入
包的使用越来越广泛, 很多情况下导入子包会导致和真正的标准库模块发生(事实上是它们的
名字)冲突。 包模块会把名字相同的标准库模块隐藏掉, 因为它首先在包内执行相对导入, 隐藏掉
标准库模块。
为 此 ,==所有的导入现在都被认为是绝对的,也就是说这些名字必须通过 Python路径(sys.path 或是 PYTHONPATH )来访问==
12.7.4 相对导入
绝对导入特性限制了模块作者的一些特权。失去了 import 语句的自由, 必须有新
的特性来满足程序员的需求。这时候, 我们有了相对导入。 相对导入特性稍微地改变了 import 语
法, 让程序员告诉导入者在子包的哪里查找某个模块。==因为 import 语句总是绝对导入的, 所以相
对导入只应用于 from-import 语句。==
12.8 模块的其他特性
12.8.1 自动载入的模块
==当 Python 解释器在标准模式下启动时, 一些模块会被解释器自动导入, 用于系统相关操作。
唯一一个影响你的是 _builtin_ 模块, 它会正常地被载入, 这和 _builtins_ 模块相同.==
sys.modules 变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典, 模块名作为键, 它们的位置作为值。
12.8.2 阻止属性导入
如果你不想让某个模块属性被 "from module import *" 导入 , 那么你可以给你不想导入的属
性名称加上一个下划线( _ )。 不过如果你导入了整个模块或是你显式地导入某个属性(例如 import
foo._bar ), 这个隐藏数据的方法就不起作用了
十四:执行环境
14.1.4 类的实例
python 给类提供了名为call的特别方法,该方法允许程序员创建可调用的对象(实例)。默
认情况下,call()方法是没有实现的,这意味着大多数实例都是不可调用的。然而,如果在类
定义中覆盖了这个方法,那么这个类的实例就成为可调用的了。调用这样的实例对象等同于调用
call()方法
14.2 代码对象
可调用的对象是 python 执行环境里最重要的部分, 然而他们只是冰山一角。python 语句, 赋值,
表达式,甚至还有模块构成了更宏大的场面。这些可执行对象无法像可调用物那样被调用。更确切
地说,这些对象只是构成可执行代码块的拼图的很小一部分,而这些代码块被称为==代码对象==
14.3 可执行的对象声明和内建函数
Python 提供了大量的 BIF 来支持可调用/可执行对象,其中包括 exec 语句。这些函数帮助程序
员执行代码对象,也可以用内建函数 complie()来生成代码对象。
14.3.1 callable()
callable()是一个布尔函数,确定一个对象是否可以通过函数操作符(())来调用。如果函数可
调用便返回 True,否则便是 False
14.3.2 compile()
compile()函数允许程序员在运行时刻迅速生成代码对象,然后就可以用 exec 语句或者内建函
数 eval()来执行这些对象或者对它们进行求值。一个很重要的观点是:==exec 和 eval()都可以执行字符串格式的Python代码==。当执行字符串形式的代码时,每次都必须对这些代码进行字节编译处理。compile()函数提供了一次性字节代码预编译,以后每次调用的时候,都不用编译了。
compile 的三个参数都是必需的,第一参数代表了要编译的 python 代码。第二个字符串,虽然
是必需的,但通常被置为空串。该参数代表了存放代码对象的文件的名字(字符串类型)。compile 的
通常用法是动态生成字符串形式的 Python 代码, 然后生成一个代码对象——代码显然没有存放在
任何文件。
最后的参数是个字符串,它用来表明代码对象的类型。有三个可能值:
- 'eval' 可求值的表达式[和 eval()一起使用]
- 'single' 单一可执行语句[和 exec 一起使用]
- 'exec' 可执行语句组[和 exec 一起使用]
可求值表达式
>>> eval_code = compile('100 + 200', '', 'eval')
>>> eval(eval_code)
300
单一可执行语句
>>> single_code = compile('print "Hello world!"', '', 'single')
>>> single_code
>>> exec single_code
Hello world!
可执行语句组
>>> exec_code = compile("""
... req = input('Count how many numbers? ')
... for eachNum in range(req):
... print eachNum
... """, '', 'exec')
>>> exec exec_code
Count how many numbers? 6
0
1
2
3
4
5
14.3.3 eval()
14.4 执行其他(Python)程序
当讨论执行其他程序时,我们把它们分类为 python 程序和其他所有的非 python 程序,后者包
括了二进制可执行文件或其他脚本语言的源代码。我们先讨论如何运行其他的 python 程序,然后是如何用 os 模块调用外部程序。
14.4.1 导入
核心笔记:当模块导入后,就执行所有的模块
这只是一个善意的提醒:在先前的第 3 章和第 12 章已经谈过了,现在再说一次,当导入 python
模块后,就执行所有的模块!当导入 python 模块后,会执行该模块!当你导入 foo 模块时候,它运行
所有最高级别的(即没有缩进的)python 代码,比如,'main()’。如果 foo 含有 bar 函数的声明,
那么便执行 def foo(...)。 再问一次为什么会这样做呢?…… 由于某些原因, bar 必须被识别为 foo
模块中一个有效的名字,也就是说 bar 在 foo 的名字空间中,其次,解释器要知道它是一个已声明
的函数,就像本地模块中的任何一个函数。现在我们知道要做什么了,那么如何处理那些不想每次
导入都执行的代码呢?缩进它,并放入 if name == 'main' 的内部。