一、函数 函数的作用:可以计算出一个返回值,最大化代码重用,最小化代码冗余,流程的分解。 1、函数相关的语句和表达式 语句 例子 Calls myfunc(‘diege','eggs',meat=‘lit’) #使用函数 def,return,yield def adder(a,b=1,*c): return a+b+c[0] global changer(): global x;x='new' lambda Funcs=[lambad x:x**2,lambad x:x*3] 2、编写函数 def创建了一个对象并将其赋值给某一个变量名。 return将一个结果对象发送给调用者。 函数是通过赋值(对象引用)传递的。 参数通过赋值传递给函数。 global声明了一个模块级的变量并被赋值。 参数,返回值以及变量并不是声明 def <name>(arg1,age2,...,agrN): <statements> return <value 函数主体往往都包含了一个return语句(不是必须的),如果它没有出现,那么函数将会在控制流执行完函数主体时结束,技术上,没有返回值的函数自动返回了none对象。 return可以在函数主体中的任何地方出现。它表示函数调用的结束,并将结果返回至函数调用。 不要尝试判断传入参数的对象类型,这样实质上破坏了函数的灵活性,把函数限制在特定的类型上 3、作用域 作用域:变量定义以及查找的地方。 本地变量:在函数内定义的变量为本地变量。 全局变量:在文件内[模块内]函数外定义的变量为全局变量 Python创建,改变或者查找变量名都是在所谓的命名空间(一个保存变量名的地方,模块中为__main__)中进行。作用域这个术语指的就是命名空间。 也就说,在代码中变量名被赋值的位置决定了这个变量名能被访问到的范围 一个函数所有变量名都与函数的命名空间相关联。 *def内定义变量名def内使用,是本地变量 *def之中的变量名与def之外的变量名不发生冲突,使用别处相同的变量名也没问题。 函数定义了本地作用域,而模块定义了全局作用域。两作用域关系。 *内嵌的模块是全局作用域:对于外部的全局变量就成为了一个模块对象的属性 *全局作用域的作用范围仅限单个文件:不要被全局迷惑,这里的全局是一个文件的顶层的变量名,仅对这个文件内部的代码而言是全局。 Python中,没有一个无所不包的情景文件作用域。替代的方法是,变量名由模块文件隔开,必须精准地导入一个模块文件才能够使用这文件中 定义的变量名, *每次对函数的调用都创建了一个新的本地作用域。 *赋值的变量名废除声明为全局变量,否则均为本地变量。 *所用的变量名都可以归纳为本地,全局,或者内置。(内置:ptyhon预定义的__builtin__模块提供的) 变量名解析:LEGB原则 对一个def语句 *变量名引用分为三个作用域进行查找:首先查找本地,然后是函数内(如果有),之后全局,最后内置。 *默认情况下,变量名赋值会创建或改变本地变量 *全局声明将赋值变量名映射到模块文件内部的作用域。 global global语句包含关键字global *全局变量是位于模块文件内部顶层的变量名 *全局变量如果是在函数内部被赋值的话,并需经过声明 *全局变量名在函数的内部不经过声明也可以被引用 4、传递参数 参数传递:传递给函数作为其输入对象的方式 *参数的传递是通过自动将对象赋值给本地变量来实现的。 *在函数内部的参数名的赋值不会影响调用者。 *改变函数的可变对象参数的值也许会对调用者有影响。 换句话说,因为参数是简单的通过赋值进行对象的传递,函数能够改变传入的可变对象,因此其结果会影响调用者。 *不可变参数是“通过值”进行传递。 像整数和字符串这样的对象是通过对象引用而不是拷贝进行传递的,但是因为你无论如何都不可能在原处改变不可变对象,实际的效果就是很像创建了一份拷贝。 可变对象是通过“指针”进行传递的。 避免可变参数的修改 在Python中,默认通过引用(也就是指针)进行函数的参数传递。如果不想在函数内部在原处的修改影响传递给它的对象。那么,能够简单的创建一个可变对象的拷贝。 我们总是能够在调用时对列表进行拷贝 L=[1,2] changer(X,L[:]) 如果不想改变传入的对象,无论函数是如何调用的,我们可以在函数内部进行拷贝,避免可变参数的修改 >>> def changer(a,b): ... a=2 ... b=b[:] ... b[0]='diege' 特定参数匹配模型 参数在ptyhon中总是通过赋值进行传递,传入的对象赋值给了在def头部的变量名。尽管这样,在模型的上层,python提供了额外的工具,该工具改变了调用过中 赋值时参数对象匹配在头部的参数名的优先级。这些工具是可选的。 总结与特定模式有关的语法: 语法 位置 解释 func(value) 调用者 常规参数,通过位置进行匹配,从左到右进行匹配 func(name=value) 调用者 关键字参数,通过变量名匹配 func(*name) 调用者 以name传递所有的对象,并作为独立的基于位置的参数 func(**name) 调用者 以name成对的传递所有的关键字/值,并作为独立的关键字的参数 def func(name) 函数 常规参数:通过位置或变量名进行匹配 def func(name=value) 函数 默认参数值:如果没有在调用中传递的话,就是用默认值 def func(*name) 函数 匹配并收集(在元组中)所有包含位置的参数 def func(**name) 函数 匹配并收集(在字典中)所有包含位置的参数。 5、匿名函数:lamdba lambad 创建了一个之后能够被调用的函数,它返回了一个函数而不是将这个函数赋值给一个变量名。 lambda表达式 lanbda arg1,arg2,...,argN:expression using arguments lambda 是一个表达式,而不是一个语句 lambda的主体是一个单个的表达式,而不是代码块 func(x,y,z):return x+y+z 默认参数也能够再lambda参数中使用,就像在def中使用一样。 lambda a='free',b='file',c='feel':a+b+c 6、作参数来应用函数 内置函数apply 当需要变得更加动态的话,可以通过将一个函数作为一个参数传递给apply来调用一个生成的函数,并且 也将传给那个函数的参数作为一个元组传递给apply函数() apply(函数,参数1(元组),参数2(元组)) 7、在序列中映射函数:map 使用内置工具map,map函数会对一个序列对象中的每一个元素应用被传入的函数,并且返回一个包含了所有函数调用结果的一个列表。 map(函数,传入函数的序列对象) >>> def inc(x):return x+10 >>> L=[1,2,3,4,5] >>> map(inc,L) [11, 12, 13, 14, 15] >>> L=[1,2,3,4,5] map嵌套lambda >>> map((lambda x:x+3),L) [4, 5, 6, 7, 8] 高级功能:提供了多个序列作为参数,它能够并行返回分别以每个序列的元素作为【函数对应参数】得到的结果的列表 >>> pow(3,4) 81 >>> map(pow,[1,2,3],[2,3,4]) #1**2,2**3,3**4 [1, 8, 81] 8、函数式编程工具:filter和reduce 函数式编程的意思就是对序列应用一些函数的工具。 基于某一测试函数过滤出一些元素-filter 对每对元素都应用函数并运行到最后结果-reduce >>> filter((lambda x:x>0),range(-5,5)) [1, 2, 3, 4] 这个等效于for range:if语句 reduce稍微复杂一点。这里两个reduce调用,计算在一个列表中所有元素加起来和以及乘起来的乘积 >>> reduce((lambda x,y:x+y),[1,2,3,4]) 10 >>> reduce((lambda x,y:x*y),[1,2,3,4]) 24 依次计算 9、重访列表解析:映射 1)、列表解析基础 >>> [x**2 for x in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> map((lambda x:x**2),range(10)) [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 2)、增加测试和嵌套循环 在for之后编写if分支,用来增加逻辑选择,if分支相当filter >>> [x for x in range(5) if x%2==0] [0, 2, 4] >>> filter((lambda x:x%2==0),range(5)) [0, 2, 4] 3)filter出来的列表可以作为map的第2个参数 >>> map((lambda x:x**2),filter((lambda x:x%2==0),range(5))) [0, 4, 16] 列表解析和map的对比 两者类似,都会收集对序列或其他可迭代对象中每个元素应用元算后的结果(一次一个项目),从而创建新列表。其主要差异在于。 map会对每个元素应用函数,而列表解析则是应用任意的表达式。因此,列表解析更通用一些,可以像map那样应用函数调用表达式, 但是,map需要一个函数才能应用其他种类的表达式(函数是map的第一参数).列表解析也支持扩展语法,如果,嵌套for循环和if 分句从而可以包含内置函数filter的功能。 10、重返迭代器:生成器 编写的函数能够返回一个值,并且稍后还可以从它刚才离开的地方仍然返回值。这样的函数被认作是生成器,因为它们随时间生成一个序列的值。 大多数方面生成器函数就像一般函数,在Python它们被自动用作实现迭代协议,因它只能够再迭代的语境中出现。 生成器和一般的函数之间代码上最大的不同就是一个生成器yield一个值,而不是return一个值。yield语句将会将函数关起,并向它的调用者返回一个值 但是保存足够的状态信息为了让其能够在函数从它挂起的地方恢复。 包含yield的语句的函数将会特地编译成为生成器。当调用它时,他们返回一个生成器对象,这个生成器对象支持迭代器对象接口。 >>> def Dtest(N): ... for i in range(N): ... yield i**2 使用 >>> for i in Dtest(5): ... print i,':', ... 0 : 1 : 4 : 9 : 16 : 查看过程 >>> x=Dtest(4) >>> x.next() 0 >>> x.next() 11、函数设计概念 *耦合性:对于输入使用参数,并且对于输出使用return语句 *耦合性:只有在真正必要的情况下使用全局变量。 *耦合性:不要改变可变类型的参数,除非调用者希望这样做。 *聚合性:每一个函数都应该有一个单一的,同一的目标 *大小:每一个函数应该相对较小。 *耦合:避免直接改变在另一个模块文件中的变量。 函数是对象:简洁调用 二、模块 1、基本 每个文件都是一个模块,并且模块导入其他模块之后就可以使用导入模块定义的变量名。模块可以由两个语句和一个重要的内置函数进行处理。 import: 使客户端(导入者)以一个整体获取一个模块。 from:容许客户端从一个模块文件中获取特定的变量名。 reload:在不中止Python程序的情况下,提供了一个重新载入模块文件代码的方法。 import和from是赋值语句,是可执行的语句,可以嵌套到if ,def语句中和def一样import和from都是隐性赋值语句 在一个模块文件的顶层定义的所有变量名都成为了被导入的模块对象的属性。 模块至少有三个角色: 代码重用:模块还是定义变量名的空间,被认作是属性。可以被多个外部的客户端应用。 系统命名空间的划分: 现实共享服务和数据: 2、python程序构架 import如何工作 执行三个步骤 1)、找到模块文件 2)、编译成位码(需要时) 3)、执行模块的代码来创建其所定义的对象。 在之后导入相同的模块时候,会跳过这三个步骤,而只提取内存中已加载模块对象。 搜索模块 导入模块时,不带模块的后缀名,比如.py Python搜索模块的路径: 1)、程序的主目录 2)、PTYHONPATH目录(如果已经进行了设置) 3)、标准连接库目录(一般在/usr/local/lib/python2.X/) 4)、任何的.pth文件的内容(如果存在的话).新功能,允许用户把有效果的目录添加到模块搜索路径中去 .pth后缀的文本文件中一行一行的地列出目录。 这四个组建组合起来就变成了sys.path了, >>> import sys >>> sys.path 导入时,Python会自动由左到右搜索这个列表中每个目录。 第1,第3元素是自动定义的,第2,第4可以用于扩展路径,从而包括自己的源码目录。 3、模块的创建和使用。 创建模块 后缀.py文本文件,模块顶层指定的所有变量名都会变成其属性。 定义一个module.py模块 name='diege' age=18 def printer(x): print x 使用模块 import全部导入,import将整个模块对象赋值给一个变量名,模块只导入一次,因为该操作开销大 >>> import module 属性 >>> module.name 'diege' 函数 >>> module.printer('hi') hi from语句:from 将获取(复制)模块特定变量名 from会把变量名赋值到另一个作用域,所以它就可以让我们直接在脚本中使用复制后的变量名,而不是通过模块 from 模块名 import 需要复制的属性 将一个或多个变量名赋值给另一个模块中同名的对象 from 模块名 import 需要复制的属性 as 新的属性名 将一个或者多个变量名赋值给另一个模块中不同名的对象 from * 语句 from 模块名 import * 取得模块顶层所有赋了值的变量名的拷贝 >>> from module import name >>> name 'diege >>> from module import name as myname >>> myname 'diege' >>> from module import printer as PR >>> PR('hi python') hi python >>> from module import name,age 复制多个变量名时要用逗号隔开 >>> name,age ('diege', 18) >>> from module import name as myname,age as myage 复制多个变量名并改变需时需要用逗号隔开多个as >>> from module import * 4、模块命名空间 模块最好理解为变量名的封装,简而言之,模块就是命名空间(变量名建立所在的场所),而存在于模块之内的变量名就是模块对象的属性。 文件生成命名空间 *模块语句会在首次导入时执行。 *顶层的赋值语句会创建模块属性(文件顶层不在的def和class之内的,但def和class隐性创建的变量名也属于模块属性)。赋值的变量名会存储在模块的命名空间内。 *模块的命名空间能通过属性__dict__(module.__dict__)或dir(module)获取 由于导入而建立的模块的命名空间是字典,可通过模块对象相关联的内置__dict__属性读取。 dir函数查看,大至与对象的__dict__属性的键排序后的列表相等,但是它还包含了类继承的变量名。 *模块是一个独立的作用域。 5、重载模块 模块程序代码默认值对每个过程执行一次,要强制使模块代码重新载入并重新运算需要使用reload内置函数。 reload是函数,import是语句。两个语法不一样。 >>> import module >>> reload(module) <module 'module' from 'module.pyc'> >>> reload(test17) <module 'test17' from '/root/test17.py'> reload()之前需得import过一次 6、模块包 除模块名以外,导入也可以指定目录路径,Pytyh代码的目录就是称为包。因此这类导入就称为包导入 import dir1.dir2.mod from dir1.dir2.mod import x .号路径相当于机器上目录层次的路径。 dir1在容器目录dir0中,dir0这个目录可以在Python模块搜索路径中找到。 __init__.py包文件 如果选择使用包导入,那就必须遵循一条约束:包导入语句的路径的每个目录内部都必须有__init__.py这个文件, 否则导入包会失败。 dir1和dir2中必须包含__init__.py,容器目录dir0不需要这类文件。因为本身没有列在import语句中 __init__.py文件可以包含程序代码,也可以是空的。 更通常情况下,__init__.py文件扮演了包初始化的挂钩 替目录产生模块命名空间以及用目录导入实现from *行为的角色。 *包初始化: 首次导入某个目录时,会自动执行该目录下__init__.py文件中所有程序代码。 所以这个文件就是放置包内文件所需初始化的代码的场所。可以使用其初始化文件,创建所需的数据文件, 连接数据库等。 *模块命名空间的初始化: *from * 语句的行为: 作为一个高级功能,可以在__init__.py文件中使用__all__列表来定义目录以form *语句形式导入时,需要 导出什么。__all__列表是指出当包(目录—)名称使用from *的时候,应该导入的子模块名称清单。 eg: /usr/local/lib/python2.7/sqlite3/__init__.py from dbapi2 import * /usr/local/lib/python2.7/site-packages/mod_python/__init__.py __all__ = ["apache", "cgihandler", "psp", "publisher", "util", "python22"] version = "3.3.1" 常见的第三方扩展都是以包目录形式发布给用户,而不是单纯的模块列表。 这样就可以通过路径来导入 7、在模块中隐藏数据 最小化from *的破坏:_X和__all__达到隐藏变量名的目的 有种特定情况,可以把下划线放在变量名前(_X),可以防止客户端使用from * 语句导入模块名时,把其中的那些变量名赋值出去。这其实是为了把命名空间的破坏最小化而已。下划线和__all__不是私有声明,还可以通过其他导入形式修改这类变量名。例如import语句、from module import _X 以外,也可以在模块顶层把变量名的字符串列表赋值给变量__all__,以达到类似于_X命名惯例的隐藏效果【__all__是不隐藏的】 mod_python.__all__ 可以看到可以用from *语句复制那些变量名 _X和__all__ 对比 _X 隐藏了 无法from * __all__ 只显示,from *只能获取__all__中指定的,其他隐藏。 python中from *会先寻找模块内的__all__列表,有的话复制其中的变量名,如果没有定义的话,from *就会复制开头没有下划线的所有命令名。 怎么觉得__all__列表里存放的是模块呢??? 8、混合用法模式:__name__和__main__ 这是一个特殊的与模块相关的技巧,可以把文件作为模块导入,并以独立式程序的形式运行。每个模块都有个名为__name__的内置属性。Python会自动设置该属性: *如果文件是以顶层程序文件执行,在启动时,__name__就会被设置为字符串__main__ *如果文件被导入,__name__就会改设成客户端所了解模块名。 结果就是模块可以检测自己的__name__,来确定它是执行还是在导入。 定义一个文件test15.py def tester(): print "It's python test!" if __name__=='__main__': tester() 9、修改模块搜索路径 可以通过PYTHONPATH以及可能的.pth路径文件进行定制。 Python程序本身是修改sys.path的内置列表。sys.path在程序启动时就进行初始化,但那之后也可以随意对其元素进行删除,附加和重设 >>> import sys >>> sys.path # cd /tmp/ # python >>> sys.path.append('/root')【增加新的路径】 >>> sys.path ['', '/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-i386.egg', '/usr/local/lib/python2.7/site-packages/setuptools-0.6c12dev_r88846-py2.7.egg', '/usr/local/lib/python2.7/site-packages/Babel-0.9.6-py2.7.egg', '/usr/local/lib/python2.7/site-packages/Trac-0.12.3-py2.7.egg', '/usr/local/lib/python2.7/site-packages/Genshi-0.6-py2.7.egg', '/usr/local/lib/python2.7/site-packages/IniAdmin-0.2_r10454-py2.7.egg', '/usr/local/lib/python2.7/site-packages/TracAccountManager-0.4dev_r11251-py2.7.egg', '/usr/local/lib/python2.7/site-packages/SvnAuthzAdminPlugin-0.2-py2.7.egg', '/usr/local/lib/python27.zip', '/usr/local/lib/python2.7', '/usr/local/lib/python2.7/plat-freebsd8', '/usr/local/lib/python2.7/lib-tk', '/usr/local/lib/python2.7/lib-old', '/usr/local/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/site-packages', '/root'] 导入/root目录下test17.py,注意启动python时在/tmp目录,所以/root不是程序启动目录 >>> import test17 >>> dir(test17) >>> test17.lessthan(3,4) True 10、相对导入语法 from语句现在可以使用点号(.)更倾向于同一个包内的模块(称为包相对导入),而不是位于模块导入搜索路径上其他地方的模块(所谓的绝对导入) *现在,可以使用点号指出该导入应该与其所在包相关联:这类导入倾向于导入位于该包内的模块,而不是导入搜索路径sys.path上其他地方的同名模块 from .apache CallBack as CB 同一个包内导入apache模块CallBack为CB变量 11、模块设计理念 *总是在Python的模块内编写代码 *模块耦合要降到最底:全局变量。模块应该尽可能和其他模块的全局变量无关。 *最大化模块的沾合性:统一目标 *模块应该少去修改其他模块的的变量。 模块是对象:元程序 模块通过内置属性显示了他们大多数的特性。因此,可很容易的编写程序来管理其他程序。我们通常称这类管理程序为元程序,因为他们是在其他系统上工作。这也称为内省,因为程序能看见和处理对象的内部。内省是高级功能,但是它可以做创建程序工具,取得模块内名为name的属性,方法包括 (1)可以使用结合点号运算, (2)或者对模块的属性字典进行索引运算(在内置__dict__属性中显示)。 (3)Python也在sys.modules字典中导出所有已经加载的模块。 (4)并提供一个内置函数getattrr,让我们以字符串名来取出属性。(就好像object.attr,而attr是运行时的字符串) >>> test17.name 'diege' >>> test17.__dict__.keys() >>> test17.__dict__['name'] 'diege >>> test17.__dict__['lessthan'] <function lessthan at 0x28495844> >>> sys.modules 显示所有加载的模块 >>> sys.modules['test17'] <module 'test17' from '/root/test17.py'> >>> sys.modules['test17'].name 'diege >>> getattr(test17,'lessthan') <function lessthan at 0x28495bc4> 12、模块陷阱 1)顶层代码的语句次序的重要性 *在导入时,模块文件顶层的程序代码(不在函数内)一旦python运行,就会立刻执行。因此,该语句是无法引用文件后面位置赋值的变量名。 *位于函数主体内的代码知道函数被调用后才会运行。 作为一条原则,如果需要把立即执行的代码和def一起混用,就要把def放在文件前面,把顶层代码放在后面。这样的话,你的函数在使用的代码运行时,可以保证他们都已定义并赋值过了。 2)通过变量名字符串导入模块的方法 import或from语句内的模块名是”硬编码“的变量名。 >>> x='string' >>> import x Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named x 这里python会试着导入文件x.py 为了避免发生这样的问题,通常的做法就是把import语句构造成python代码的字符串,再传给exec语句执行: >>> modname='string' >>> exec "import "+modname exec语句以及和他类似eval会编译一段字符串代码,将其传给Python解析器来执行。 3)from复制变量名,而不是连接 from语句其实是在导入者的作用域内对变量名的赋值语句,也就是变量名拷贝运算,而不是变量名的别名机制。它的实现和python所有赋值运算都一样,微妙之处在于,共享对象的代码存在于不同的文件中。然后,我们使用import获得了整个模块,然后赋值某个点号运算的变量名,就会修改导入的模块中的变量名。点号运算把python定向到了模块对象,而不是赋值模块中对象。 4)from*会让变量语义模糊 5)reload不会影响from导入 6)不要使用reload、from以及交互模式测试 reload中引用模块得通过import至少将其加载一次: 不要from导入之后reload 7) reload使用没有传递性 当重载一个模块时,Python只会重载那个模块的文件,不会自动重载该文件重载嘶碰巧还要导入的模块。 8)递归形式的from import无法工作 不要在递归导入中使用 from。 |
|