十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
在Python语言中,可以在函数中定义函数。 这种在函数中嵌套定义的函数也叫内部函数。我们来看下面的代码:
目前创新互联公司已为数千家的企业提供了网站建设、域名、虚拟空间、网站改版维护、企业网站设计、武川网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
上述代码中,定义了函数greet,在函数greet内部又定义了一个函数inner_func, 并调用该函数打印了一串字符。
我们可以看到,内部函数inner_func的定义和使用与普通函数基本相同。需要注意的是变量的作用域,在上述代码中,函数参数name对于全局函数greet是局部变量,对内部函数inner_func来说则是非局部变量。内部函数对于非局部变量的访问规则类似于标准的外部函数访问全局变量。
从这个例子我们还可以看到内部函数的一个作用,就是通过定义内部函数的方式将一些功能隐藏起来,防止外部直接调用。常见的场景是,在一个复杂逻辑的函数中,将一些小的任务定义成内部函数,然后由这个外层函数使用,这样可以使代码更为清晰,易于维护。这些内部函数只会在这个外层函数中使用,不能被其他函数或模块使用。
在Python语言中, 函数也是对象,它可以被创建、赋值给变量,或者作为函数的返回值。我们来看下面这个例子。
在上述代码中,在函数gen_greet内部定义了inner_func函数,并返回了一个inner_func函数对象。外部函数gen_greet返回了一个函数对象,所以像gen_greet这样的函数也叫工厂函数。
在内部函数inner_func中,使用了外部函数的传参greet_words(非局部变量),以及函数的参数name(局部变量),来打印一个字符串。
接下来,调用gen_greet("Hello")创建一个函数对象say_hello,紧接着调用say_hello("Mr. Zhang"),输出的结果为:Hello, Mr. Zhang!
同样的,调用gen_greet("Hi")创建一个函数对象say_hi,调用say_hello("Mr. Zhang"),输出的结果为:Hi,Tony!
我们可以发现,gen_greet返回的函数对象具有记忆功能,它能够把所需使用的非局部变量保存下来,用于后续被调用的时候使用。这种保存了非局部变量的函数对象被称作闭包(closure)。
那么闭包是如何实现的呢?其实并不复杂,函数对象中有一个属性__closure__,它就是在创建函数对象时用来保存这些非局部变量的。
__closure__属性是一个元组或者None类型。在上述代码中,我们可以通过下面方式查看:
函数的嵌套所实现的功能大都可以通过定义类的方式来实现,而且类是更加面向对象的代码编写方式。
嵌套函数的一个主要用途是实现函数的装饰器。我们看下面的代码:
在上述代码中,logger函数返回函数with_logging,with_logging则是打印了函数func的名称及传入的参数,然后调用func, 并将参数传递给func。其中的@wraps(func)语句用于复制函数func的名称、注释文档、参数列表等等,使得with_logging函数具有被装饰的函数func相同的属性。
代码中接下来用@logger对函数power_func进行修饰,它的作用等同于下面的代码:
可见,装饰器@符其实就是上述代码的精简写法。
通过了解了嵌套函数和闭包的工作原理,我们在使用过程中就能够更加得心应手了。
自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.
也就是运行时能够获得对象的类型.比如type(),dir(),getattr(),hasattr(),isinstance()
a = [1,2,3]
b = {'a':1,'b':2,'c':3}
c = True
print(type(a),isinstance(b,dict))
2 Python 中单下划线和双下划线
_xxx 不能用'from module import *'导入 以单下划线开头的表示的是保护类型的变量。即保护类型只能允许其本身与子类进行访问。
_ _xxx__ 系统定义的是特列方法。像__init__之类的
__xxx 类中的私有变量名 双下划线的表示的是私有类型的变量。只能是允许这个类本身进行访问了。连子类也不可以
3 python中的不定长参数 *args and **kwargs
不确定函数里传递参数个数时可以用*args
def getall(*args):
for item in args:
print(item)
getall('a','b','c')
getall(*['a','b','c']) # 打散
使用没有事先定义的参数名kwargs**
def get_undifined(**kwargs):
for item in kwargs.items():
print(item[0],item[1])
get_undifined(name='amy',age=14) # 打散
get_undifined(**{'a':1,'b':'hello'}) # 打散
注意两者共同使用的时候 位置参数 要在前
4 python中装饰器 与 AOP 面向切面编程
切面需求: 较为经典的有插入日志、性能测试、事务处理等
而装饰器这种设计模式 是解决这类问题的绝佳途径,可以为已经存在的对象添加额外的功能
5 python中鸭子模型
会鸭子叫的就是鸭子,不关心对象是什么类型,只关心对象的特性。
比如众多的文件句柄,可迭代对象等具有同一类特征的不同数据对象
鸭子类型在动态语言中经常使用,非常灵活
6 Python中的作用域
python中,一个变量的作用域总是由在代码中被赋值的地方所决定的。
Python变量的搜索顺序:
本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)
7 python中的语法结构闭包
闭包(closure)是函数式编程的重要的 语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:
1 必须有一个内嵌函数
2 内嵌函数必须引用外部函数中的变量
3 外部函数的返回值必须是内嵌函数
8 python 的 垃圾回收机制
Python垃圾回收机制
Python GC主要使用 引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)
解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。
1 引用计数
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,
当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
2 标记-清除机制
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,
把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
3 分代技术
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,
垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
9 python种类以及与其他计算机语言类型对比
C语言: 代码编译得到机器码 ,机器码在处理器上直接执行,每一条指令控制CPU工作–速度较快
Java,c#,python,php 其他语言: 代码编译得到字节码 ,虚拟机执行字节码并转换成机器码再后在处理器上执行—-速度慢
python的种类
Cpython
Python的官方版本,使用C语言实现,使用最为广泛,
CPython实现会将源文件(py文件)转换成字节码文件
(pyc文件),然后运行在Python虚拟机上。
Jyhton
Python的Java实现,Jython会将Python代码动态编译成Java
字节码,然后在JVM上运行。
IronPython
Python的C#实现,IronPython将Python代码编译成C#字节
码,然后在CLR上运行。(与Jython类似)
PyPy(特殊)
Python实现的Python,将Python的字节码字节码再编译成机器
码。
9 python PEP8 编码规范
一 代码编排
1 缩进。4个空格的缩进(编辑器都可以完成此功能),不使用Tap,更不能混合使用Tap和空格。
2 每行最大长度79,换行可以使用反斜杠,最好使用圆括号。换行点要在操作符的后边敲回车。
3 类和top-level函数定义之间空两行;类中的方法定义之间空一行;函数内逻辑无关段落之间空一行;其他地方尽量不要再空行。
二 文档编排
1 模块内容的顺序:模块说明和docstring—import—globalsconstants—其他定义。其中import部分,又按标准、三方和自己编写顺序依次排放,之间空一行。
2 不要在一句import中多个库,比如import os, sys不推荐。
3 如果采用from XX import XX引用库,可以省略‘module.’,都是可能出现命名冲突,这时就要采用import XX。
三 空格的使用
总体原则,避免不必要的空格。
1 各种右括号前不要加空格。
2 逗号、冒号、分号前不要加空格。
3 函数的左括号前不要加空格。如Func(1)。
4 序列的左括号前不要加空格。如list[2]。
5 操作符左右各加一个空格,不要为了对齐增加空格。
6 函数默认参数使用的赋值符左右省略空格。
7 不要将多句语句写在同一行,尽管使用‘;’允许。
8 if/for/while语句中,即使执行语句只有一句,也必须另起一行。
四 注释
注释必须使用英文,最好是完整的句子,首字母大写,句后要有结束符,结束符后跟两个空格,开始下一句。如果是短语,可以省略结束符。
1 块注释,在一段代码前增加的注释。在‘#’后加一空格。段落之间以只有‘#’的行间隔。比如:
2 行注释,在一句代码后加注释。比如:x = x + 1 # Increment x但是这种方式尽量少使用。
3 避免无谓的注释。
五 文档描述
1 为所有的共有模块、函数、类、方法写docstrings;非共有的没有必要,但是可以写注释(在def的下一行)。
2 如果docstring要换行,参考如下例子,详见PEP 257
六 命名规范
新编代码必须按下面命名风格进行,现有库的编码尽量保持风格。
1 尽量单独使用小写字母‘l’,大写字母‘O’等容易混淆的字母。
2 模块命名尽量短小,使用全部小写的方式,可以使用下划线。
3 包命名尽量短小,使用全部小写的方式,不可以使用下划线。
4 类的命名使用CapWords的方式,模块内部使用的类采用_CapWords的方式。
5 异常命名使用CapWords+Error后缀的方式。
6 全局变量尽量只在模块内有效,类似C语言中的static。实现方法有两种,一是__all__机制;二是前缀一个下划线。
7 函数命名使用全部小写的方式,可以使用下划线。
8 常量命名使用全部大写的方式,可以使用下划线。
9 类的属性(方法和变量)命名使用全部小写的方式,可以使用下划线。
9 类的属性有3种作用域public、non-public和subclass API,可以理解成C++中的public、private、protected,non-public属性前,前缀一条下划线。
11 类的属性若与关键字名字冲突,后缀一下划线,尽量不要使用缩略等其他方式。
12 为避免与子类属性命名冲突,在类的一些属性前,前缀两条下划线。比如:类Foo中声明__a,访问时,只能通过Foo._Foo__a,避免歧义。如果子类也叫Foo,那就无能为力了。
13 类的方法第一个参数必须是self,而静态方法第一个参数必须是cls。
七 编码建议
1 编码中考虑到其他python实现的效率等问题,比如运算符‘+’在CPython(Python)中效率很高,都是Jython中却非常低,所以应该采用.join()的方式。
2 尽可能使用‘is’‘is not’取代‘==’,比如if x is not None 要优于if x。
3 使用基于类的异常,每个模块或包都有自己的异常类,此异常类继承自Exception。
4 异常中不要使用裸露的except,except后跟具体的exceptions。
5 异常中try的代码尽可能少。比如:
6 使用startswith() and endswith()代替切片进行序列前缀或后缀的检查。比如
7 使用isinstance()比较对象的类型。比如
8 判断序列空或不空,有如下规则
9 字符串不要以空格收尾。
10 二进制数据判断使用 if boolvalue的方式。
在函数中可以定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。
闭包可以用来在一个函数与一组私有变量之间创建关联关系。
在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
形成闭包的三个条件
必须有一个内嵌函数—这对应函数之间的嵌套;
内嵌函数必须引用一个定义在闭合范围内的变量—内部函数引用外部变量;
外部函数必须返回内嵌函数—必须返回内部函数。
换句话来说:闭包的概念很简单,一个可以引用在函数闭合范围内变量的函数,即内部函数,只有那个内部函数才有所谓的__closure__属性。
闭包的原理
形成闭包之后,闭包函数会获得一个非空的_Closure_属性,这个属性是一个元组。
组里面的对象为cell对象,而访问cell对象的cell_contents属性则可以得到闭包变量的当前值。
而随着闭包的继续调用,变量会进行再次更新。由此可见,一般形成闭包之后,Python确定会将_closure_和闭包函数绑定作为储存闭包变量的场所。
闭包的好处是什么?
其实,闭包并不是必须的。
没有闭包的话,Python的功能不会受到任何影响;但有了闭包之后,可以提供一种额外的解决方案。
在python中,函数可以被嵌套定义,也就是说,函数中可以定义函数。该函数还可以将其内部定义的函数作为返回值返回。
闭包的定义:一般来说,我们可以认为,如果一个函数可以读取其他函数中的局部变量,那么它们就构成了闭包。
注意 :闭包的定义不是特别清晰,但大体上的意思是这样的。
我们知道,普通的函数是可以使用全局变量的
类似的,函数中定义的函数,也是可以使用外部函数的变量的。因此,满足了函数读取了其他函数局部变量的这一条件,他们因此构成了闭包。
在闭包的使用中,我们可以先给外部的函数赋予不同的局部变量,然后再调用其中内部的函数时,就可以读取到这些不同的局部变量了。
外部变量的使用 在普通函数中,虽然可以直接使用全局变量,但是不可以直接修改全局变量。从变量的作用域来说,一旦你尝试修改全局变量,那么就会尝试创建并使用一个同名的局部变量。因此,如果你需要在普通函数中修改全局变量,需要使用global
同样的,如果你希望通过定义在内部的函数去修改其外部函数的变量,那么必须使用nonlocal
之前 分析了装饰器的语法,由此可以直接推导出其基本框架。但为了写出一个功能完整的装饰器,还需要了解一个概念——闭包。
闭包(closure) ,是引用了自由变量的函数。 这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
看下面的例子
对 f 内部的函数 g 来说,参数 a 既不是它的参数,也不是它的局部变量,而是它的自由变量。该自由变量可以
闭包和嵌套函数的概念有所区别。闭包当然是嵌套函数,但没有引用自由变量的嵌套函数却不是闭包。
Python 的函数有一个只读属性 __closure__ ,存储的就是函数所引用的自由变量,
如果仅仅是嵌套函数,它的 __closure__ 应该是 None 。
闭包有个重要的特性:内部函数只能引用而不能修改外部函数中定义的自由变量。试图直接修改只有两种结果,要么运行出错,要么你以为你修改了,实际并没有。
不能修改不是因为 Python 设计者故意限制,不给它权限,而是外部的自由变量被内部的局部变量覆盖了;被覆盖了也不是闭包独有的特性,从普通函数内部同样也不能直接修改全局变量。Python 命名空间的查找规则简写为 LEGB,四个字母分别代表 local、enclosed、global 和 build-in,闭包外层函数的命名空间就是 enclosed。Python 在检索变量时,按照 L - E - G - B 的顺序依次查找,如果在 L 中找到了变量,就不会继续向后查找。
在示例 1 中,你的本意是修改自由变量 number ,然而并不能:由于存在对 number 的赋值语句( number += 1 ),Python 会认为 number 是 printer 的局部变量,可是在局部变量字典中又查找不到它的定义,只好抛出异常。抛出的异常不是因为不能修改自由变量,而是局部变量还没赋值就被引用了。
在示例 2 中,Python 成功地在 printer 内定义了局部变量 number ,并覆盖了同名自由变量,你可能以为自己成功修改了 print_msg 中的 number ,然而并没有。
怎么才能修改呢?
一种做法是利用可变类型(mutable)的特性,把变量存放在列表(List)之中。对可变的列表的修改并不需要对列表本身赋值, number[0] = 3 只是修改了列表元素。虽然列表发生了变化,但引用列表的变量却并没有改变,巧妙地“瞒”过了 Python。见示例3。
Python 3 引入了 nonlocal 关键字,明确告诉解释器:这不是局部变量,要找上外头找去。在示例 4 中, nonlocal 帮助我们实现了所期望的对自由变量的修改。
其实,在 Python 2 中,用 global 代替 nonlocal ,也能达到类似的效果,但由于全局变量的不易控制,这种做法不被提倡。
下面的例子很好地展示了自由变量的特点:与引用它的函数一同存在,而想要修改它,得小心谨慎。
装饰器 rate_limit 的作用,是限制被装饰的函数每秒内最多被访问 max_per_sec 次。为此,需要维护一个变量用以记录上次被调用的时刻,它独立于函数之外,和被修饰的函数一同存在,还能在每次被调用的时候更新。 last_time_called 就是这样的变量。为了正确地更新, last_time_called 以列表的形式存在。如果在 Python 3 中,它也可以直接存为 float ,只要在内部函数中声明为 nonlocal ,也可以达到同样的目的。