包(lib)、模块(module)
在Python中,存在包和模块两个常见概念。
模块:编写Python代码的py文件
包:用来分门别类存放模块代码的文件夹,【一般存在一个__init__.py文件】
模块的几种导入方式:
__init__.py文件:
__all__变量的使用:
__init__.py文件中导入其他模块文件,推荐使用
from . import xxx
可以使用别名来简化导入的模块名称【as的使用】。
注意:
1、当一个普通文件夹充当包的时候,导入模块时,一定要指定模块名称,因为普通文件夹不是包(包是可以直接导入的)。
2、当导入的包路径太长的时候,可以使用as关键字取别名来解决
3、包与普通文件夹的区别
发布模块:
在文件的主目录创建setup.py文件,编辑如下:
from distutils.core import setup
setup(name=”ljh”,version=”1.0”,description=”描述”,author=”作者”,author_email=”作者邮箱”,py_modules=[“xx.xx.xx.py”,”xx.xx.xxx.py”])
如下图所示:
执行构建命令:
python setup.py build
执行打包命令:
python setup.py sdist
之后项目中结构如下:
执行安装命令:
python setup.py install
就会将我们打包后的压缩包安装到Python对应的第三方模块下:
python36\Lib\site-packages
我们在代码中就可以导入使用这个模块了。
1.2==,is的使用
总结
is是比较两个引用是否指向了同一个对象(地址引用比较)。
==是比较两个对象是否相等。(比较的数值)
1.3 深拷贝、浅拷贝
1.3.1 赋值
将一个变量赋值给另一个变量,这个过程叫做赋值。赋值会导致多个变量同时指向一块内存,所以此时不管是==或者is都返回True
所以当一个发送变量,另一个也随之发送变化。
1.3.2 浅拷贝(copy)
浅拷贝是对于一个对象的顶层拷贝
通俗的理解是:拷贝了引用,并没有拷贝内容
但是,当a发送变化时,b不会变化:
1.3.3 深拷贝
深拷贝是对于一个对象所有层次的拷贝(递归)
1.3.4拷贝的其他方
注意常量类型的深浅拷贝问题(如字符串、小整形数值型、元组)
1.4属性property
面试题:
1、你对面向对象的理解
2、 面向对象的特征是什么
封装、继承、多态
3、对封装的理解?
封装,类本身就是一个封装,封装了属性和方法。方法也是封装,对一些业务逻辑的封装。私有也是封装,将一些方法和属性私有化,对外提供可访问的接口。
4、对继承的理解
将共性的内容放在父类中,子类只需要关注自己特有的内容,共性的继承过来就行了。
这样简化开发,符合逻辑习惯,利于扩展。
5、多态的理解
多态,一个对象在不同的情况下显示不同的形态。在python中因为是弱类型语言,对类型没有限定,所有python中不完全支持多态,但是多态的思想呢,python也是能体现的。
property是属于类的,不能写到方法中,写类里,property是一个方法,可以传递属性property(getName,setName)是值传递引用传递,不是调用,把函数传递里面,不是返回值,name = property(getName,setName)
1.4.1私有属性添加getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
def getMoney(self):
return self.__money
def setMoney(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
1.4.2 使用property升级getter和setter方法
class Money(object):
def __init__(self):
self.__money = 0
def getMoney(self):
return self.__money
def setMoney(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
money = property(getMoney, setMoney)
运行结果:
In [1]: from get_set import Money
In [2]:
In [2]: a = Money()
In [3]:
In [3]: a.money
Out[3]: 0
In [4]: a.money = 100
In [5]: a.money
Out[5]: 100
In [6]: a.getMoney()
Out[6]: 100
1.4.3使用property取代getter和setter方法
@property成为属性函数,可以对属性赋值时做必要的检查,并保证代码的清晰短小,主要有2个作用
将方法转换为只读
重新实现一个属性的设置和读取方法,可做边界判定
class Money(object):
def __init__(self):
self.__money = 0
@property
def money(self):
return self.__money
@money.setter
def money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
运行结果
In [3]: a = Money()
In [4]:
In [4]:
In [4]: a.money
Out[4]: 0
In [5]: a.money = 100
In [6]: a.money
Out[6]: 100
1.5 生成器
[if !supportLists]1.5.1 [endif]什么是生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
[if !supportLists]1.5.2 [endif]创建生成器方法1
要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的[ ]改成 ( )
In [15]: L = [ x*2 for x in range(5)]
In [16]: L
Out[16]: [0, 2, 4, 6, 8]
In [17]: G = ( x*2 for x in range(5))
In [18]: G
Out[18]: at 0x7f626c132db0>
In [19]:
创建L和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是一个列表,而 G 是一个生成器。我们可以直接打印出L的每一个元素,但我们怎么打印出G的每一个元素呢?如果要一个一个打印出来,可以通过next()函数获得生成器的下一个返回值:
In [19]: next(G)
Out[19]: 0
In [20]: next(G)
Out[20]: 2
In [21]: next(G)
Out[21]: 4
In [22]: next(G)
Out[22]: 6
In [23]: next(G)
Out[23]: 8
In [24]: next(G)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
in ()
----> 1 next(G)
StopIteration:
In [25]:
In [26]: G = ( x*2 for x in range(5))
In [27]: for x in G:
....: print(x)
....:
0
2
4
6
8
In [28]:
生成器保存的是算法,每次调用next(G),就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。当然,这种不断调用 next() 实在是太变态了,正确的方法是使用 for 循环,因为生成器也是可迭代对象。所以,我们创建了一个生成器后,基本上永远不会调用 next() ,而是通过 for 循环来迭代它,并且不需要关心 StopIteration 异常。
[if !supportLists]1.5.3 [endif]创建生成器方法2
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
In [28]: def fib(times):
....: n = 0
....: a,b = 0,1
....: while n
....: print(b)
....: a,b = b,a+b
....: n+=1
....: return 'done'
....:
In [29]: fib(5)
1
1
2
3
5
Out[29]: 'done'
仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:
In [30]: def fib(times):
....: n = 0
....: a,b = 0,1
....: while n
....: yield b
....: a,b = b,a+b
....: n+=1
....: return 'done'
....:
In [31]: F = fib(5)
In [32]: next(F)
Out[32]: 1
In [33]: next(F)
Out[33]: 1
In [34]: next(F)
Out[34]: 2
In [35]: next(F)
Out[35]: 3
In [36]: next(F)
Out[36]: 5
In [37]: next(F)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
in ()
----> 1 next(F)
StopIteration: done
在上面fib的例子,我们在循环过程中不断调用 yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代:
In [38]: for n in fib(5):
....: print(n)
....:
1
1
2
3
5
In [39]:
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
In [39]: g = fib(5)
In [40]: while True:
....: try:
....: x = next(g)
....: print("value:%d"%x)
....: except StopIteration as e:
....: print("生成器返回值:%s"%e.value)
....: break
....:
value:1
value:1
value:2
value:3
value:5
生成器返回值:done
In [41]:
[if !supportLists]1.5.4 [endif]send
例子:执行到yield时,gen函数作用暂时保存,返回i的值;temp接收下次c.send("python"),send发送过来的值,c.next()等价c.send(None)
In [10]:def gen():
....: i = 0
....: while i<5:
....: temp = yield i
....: print(temp)
....: i+=1
....:
使用next函数
In [11]: f = gen()
In [12]: next(f)
Out[12]: 0
In [13]: next(f)
None
Out[13]: 1
In [14]: next(f)
None
Out[14]: 2
In [15]: next(f)
None
Out[15]: 3
In [16]: next(f)
None
Out[16]: 4
In [17]: next(f)
None
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
in ()
----> 1 next(f)
StopIteration:
使用__next__()方法
In [18]: f = gen()
In [19]: f.__next__()
Out[19]: 0
In [20]: f.__next__()
None
Out[20]: 1
In [21]: f.__next__()
None
Out[21]: 2
In [22]: f.__next__()
None
Out[22]: 3
In [23]: f.__next__()
None
Out[23]: 4
In [24]: f.__next__()
None
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
in ()
----> 1 f.__next__()
StopIteration:
使用send
In [43]: f = gen()
In [44]: f.__next__()
Out[44]: 0
In [45]: f.send('haha')
haha
Out[45]: 1
In [46]: f.__next__()
None
Out[46]: 2
In [47]: f.send('haha')
haha
Out[47]: 3
In [48]:
[if !supportLists]1.5.5 [endif]实现多任务
模拟多任务(进程,线程,协程)实现方式之一:协程
def test1():
while True:
print("--1--")
yield None
def test2():
while True:
print("--2--")
yield None
t1 = test1()
t2 = test2()
while True:
t1.__next__()
t2.__next__()
总结
生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第n次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
[if !supportLists]1. [endif]节约内存
[if !supportLists]2. [endif]迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
[if !supportLists]1.6 [endif]迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
[if !supportLists]1.6.1 [endif]可迭代对象
以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、 tuple 、 dict 、 set 、 str 等;
一类是generator,包括生成器和带 yield 的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象: Iterable 。
[if !supportLists]1.6.2 [endif]判断是否可以迭代
可以使用isinstance()判断一个对象是否是 Iterable 对象:
In [50]: from collections import Iterable
In [51]: isinstance([], Iterable)
Out[51]: True
In [52]: isinstance({}, Iterable)
Out[52]: True
In [53]: isinstance('abc', Iterable)
Out[53]: True
In [54]: isinstance((x for x in range(10)), Iterable)
Out[54]: True
In [55]: isinstance(100, Iterable)
Out[55]: False
而生成器不但可以作用于for循环,还可以被 next() 函数不断调用并返回下一个值,直到最后抛出 StopIteration 错误表示无法继续返回下一个值了。
[if !supportLists]1.6.3 [endif]迭代器
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是 Iterator 对象:
In [56]: from collections import Iterator
In [57]: isinstance((x for x in range(10)), Iterator)
Out[57]: True
In [58]: isinstance([], Iterator)
Out[58]: False
In [59]: isinstance({}, Iterator)
Out[59]: False
In [60]: isinstance('abc', Iterator)
Out[60]: False
In [61]: isinstance(100, Iterator)
Out[61]: False
[if !supportLists]1.6.4 [endif]iter()函数
生成器都是Iterator对象,但 list 、 dict 、 str 虽然是 Iterable ,却不是 Iterator 。
把list、 dict 、 str 等 Iterable 变成 Iterator 可以使用 iter() 函数:
In [62]: isinstance(iter([]), Iterator)
Out[62]: True
In [63]: isinstance(iter('abc'), Iterator)
Out[63]: True
总结
[if !supportLists]· [endif]凡是可作用于for循环的对象都是 Iterable 类型;
[if !supportLists]· [endif]凡是可作用于next()函数的对象都是 Iterator 类型
[if !supportLists]· [endif]集合数据类型如list、 dict 、 str 等是 Iterable 但不是 Iterator ,不过可以通过 iter() 函数获得一个 Iterator 对象。
[if !supportLists]· [endif]目的是在使用迭代器的时候,减少内存的占用。
[if !supportLists]1.7 [endif]函数
递归、函数变量赋值、参数中的函数、匿名函数、闭包、偏函数
递归:
函数的递归,就是让在函数的内部调用函数自身的情况,这个函数就是递归函数。
注意:递归一定要有结束条件,否则就会成为一个死循环。
函数变量赋值:
我们可以将一个函数赋值给一个变量(注意,不是调用哦),这个这个变量也会指向该函数。
参数中的函数:
函数作为一个对象,我们同样可以将函数当成一个实际参数传递给另一个函数进行处理,这个是动态语言才用的特性,也使得动态语言变得相当的灵活和好用哦~~~
匿名函数:
例如:前面我们使用的在一个函数中,参数是另外一个函数。常规方式当我们调用的时候,需要创建一个函数,昨晚调用函数的参数。但是我们也可以使用匿名函数来完成。
匿名函数的格式如下:
lambda [参数列表]://代码【返回值不需要写return】
这样写的好处就是,代码简化,坏处就是降低了阅读性。
偏函数:
常规函数操作中,我们在函数的参数中可以添加参数的默认值来简化函数的操作,偏函数也可以做到这一点,偏函数可以在一定程度上更加方便的管理我们的函数操作
偏函数通过内置模块functools的partial函数进行定义和处理。
# functools.partial()函数语法结构
新函数名称= functools.partial(函数名称, 默认赋值参数)
# 原始的2进制数据转换 int("111", base=2)
~执行结果:7
# 引入我们需要的模块functools import functools
# 通过偏函数扩展一个新的函数int2
int2 = functools.partial(int, base=2)
# 使用新的函数,新的函数等价于上面的
int("111", base=2)
int2("111")
~执行结果:7
闭包(closure):
javascript也是解释型语言,也有闭包的概念
[if !supportLists]1.7.1 [endif]函数引用
def test1():
print("--- in test1 func----")
#调用函数
test1()
#引用函数
ret = test1
print(id(ret))
print(id(test1))
#通过引用调用函数
ret()
运行结果:
--- in test1 func----
140212571149040
140212571149040
--- in test1 func----
[if !supportLists]1.7.2 [endif]什么是闭包
#定义一个函数
def test(number):
#在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包
def test_in(number_in):
print("in test_in函数, number_in is %d"%number_in)
return number+number_in
#其实这里返回的就是闭包的结果
return test_in
#给test函数赋值,这个20就是给参数number
ret = test(20)
#注意这里的100其实给参数number_in
print(ret(100))
#注意这里的200其实给参数number_in
print(ret(200))
运行结果:
intest_in函数, number_in is 100
120
intest_in函数, number_in is 200
220
[if !supportLists]1.7.3 [endif]看一个闭包的实际例子:
def line_conf(a, b):
def line(x):
return a*x + b
return line
line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5))
print(line2(5))
这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。
如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。
闭包思考:
1.闭包似优化了变量,原来需要类对象完成的工作,闭包也可以完成
2.由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存
[if !supportLists]1.8 [endif]装饰器
装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题,但对于好多初次接触这个知识的人来讲,这个功能有点绕,自学时直接绕过去了,然后面试问到了就挂了,因为装饰器是程序开发的基础知识,这个都不会,别跟人家说你会Python, 看了下面的文章,保证你学会装饰器。
装饰器,功能就是在运行原来功能基础上,加上一些其它功能,比如权限的验证,比如日志的记录等等。不修改原来的代码,进行功能的扩展。
比如java中的动态代理,python的注解装饰器
其实python的装饰器,是修改了代码。
[if !supportLists]1.8.1 [endif]装饰器的理解
1、先明白这段代码
####第一波 ####
def foo():
print('foo')
foo #表示是函数
foo() #表示执行foo函数
####第二波 ####
def foo():
print('foo')
foo = lambda x: x + 1
foo() #执行下面的lambda表达式,而不再是原来的foo函数,因为foo这个名字被重新指向了另外一个匿名函数
2、需求来了
初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:
###############基础平台提供的功能如下 ###############
def f1():
print('f1')
def f2():
print('f2')
def f3():
print('f3')
def f4():
print('f4')
###############业务部门A 调用基础平台提供的功能 ###############
f1()
f2()
f3()
f4()
###############业务部门B 调用基础平台提供的功能 ###############
f1()
f2()
f3()
f4()
目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。
老大把工作交给Low B,他是这么做的:
跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。太棒了,有充足的时间泡妹子...
当天Low B被开除了…
老大把工作交给Low BB,他是这么做的:
###############基础平台提供的功能如下 ###############
def f1():
#验证1
#验证2
#验证3
print('f1')
def f2():
#验证1
#验证2
#验证3
print('f2')
def f3():
#验证1
#验证2
#验证3
print('f3')
def f4():
#验证1
#验证2
#验证3
print('f4')
###############业务部门不变 ###############
###业务部门A 调用基础平台提供的功能###
f1()
f2()
f3()
f4()
###业务部门B 调用基础平台提供的功能 ###
f1()
f2()
f3()
f4()
过了一周Low BB被开除了…
老大把工作交给Low BBB,他是这么做的:
只对基础平台的代码进行重构,其他业务部门无需做任何修改
###############基础平台提供的功能如下 ###############
def check_login():
#验证1
#验证2
#验证3
pass
def f1():
check_login()
print('f1')
def f2():
check_login()
print('f2')
def f3():
check_login()
print('f3')
def f4():
check_login()
print('f4')
老大看了下Low BBB的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天:
老大说:
写代码要遵循开放封闭原则(OCP),虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
[if !supportLists]· [endif]封闭:已实现的功能代码块
[if !supportLists]· [endif]开放:对扩展开发
[if !supportLists]· [endif]OCP原则,即开放关闭原则
[if !supportLists]· [endif]对已有代码的修改关闭
[if !supportLists]· [endif]对已有代码的增加开放
如果将开放封闭原则应用在上述需求中,那么就不允许在函数f1、f2、f3、f4的内部进行修改代码,老板就给了Low BBB一个实现方案:
def w1(func):
def inner():
#验证1
#验证2
#验证3
func()
return inner
@w1
def f1():
print('f1')
@w1
def f2():
print('f2')
@w1
def f3():
print('f3')
@w1
def f4():
print('f4')
对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数f1 f2 f3 f4之前都进行【验证】操作,并且其他业务部门无需做任何操作。
Low BBB心惊胆战的问了下,这段代码的内部执行原理是什么呢?
老大正要生气,突然Low BBB的手机掉到地上,恰巧屏保就是Low BBB的女友照片,老大一看一紧一抖,喜笑颜开,决定和Low BBB交个好朋友。
详细的开始讲解了:
单独以f1为例:
def w1(func):
def inner():
#验证1
#验证2
#验证3
func()
return inner
@w1
def f1():
print('f1')
python解释器就会从上到下解释代码,步骤如下:
[if !supportLists]1. [endif]def w1(func): ==>将w1函数加载到内存
[if !supportLists]2. [endif]@w1
没错,从表面上看解释器仅仅会解释这两句代码,因为函数在没有被调用之前其内部代码不会被执行。
从表面上看解释器着实会执行这两句,但是@w1这一句代码里却有大文章, @函数名 是python的一种语法糖。
上例@w1内部会执行一下操作:
执行w1函数
执行w1函数 ,并将 @w1 下面的函数作为w1函数的参数,即:@w1等价于 w1(f1) 所以,内部就会去执行:
def inner():
#验证 1
#验证 2
#验证 3
f1() # func是参数,此时 func 等于 f1
return inner#返回的 inner,inner代表的是函数,非执行函数 ,其实就是将原来的 f1 函数塞进另外一个函数中
w1的返回值
将执行完的w1函数返回值 赋值 给@w1下面的函数的函数名f1 即将w1的返回值再重新赋值给 f1,即:
新f1 =def inner():
#验证 1
#验证 2
#验证 3
原来f1()
return inner
所以,以后业务部门想要执行f1函数时,就会执行 新f1 函数,在新f1 函数内部先执行验证,再执行原来的f1函数,然后将原来f1 函数的返回值返回给了业务调用者。
如此一来,即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用着
Low BBB你明白了吗?要是没明白的话,我晚上去你家帮你解决吧!!!
[if !supportLists]1.8.2 [endif]多个装饰器
#定义函数:完成包裹数据
def makeBold(fn):
def wrapped():
return "" + fn() + ""
return wrapped
#定义函数:完成包裹数据
def makeItalic(fn):
def wrapped():
return "" + fn() + ""
return wrapped
@makeBold
def test1():
return "hello world-1"
@makeItalic
def test2():
return "hello world-2"
@makeBold
@makeItalic
def test3():
return "hello world-3"
print(test1()))
print(test2()))
print(test3()))
运行结果:
hello world-1
hello world-2
hello world-3
[if !supportLists]1.8.3 [endif]装饰器(decorator)功能
[if !supportLists]1. [endif]引入日志
[if !supportLists]2. [endif]函数执行时间统计
[if !supportLists]3. [endif]执行函数前预备处理
[if !supportLists]4. [endif]执行函数后清理功能
[if !supportLists]5. [endif]权限校验等场景
[if !supportLists]6. [endif]异常的处理
[if !supportLists]7. [endif]缓存
[if !supportLists]1.8.4 [endif]装饰器示例
[if !supportLists]1.8.4.1 [endif]例1:无参数的函数
from time import ctime, sleep
def timefun(func):
def wrappedfunc():
print("%s called at %s"%(func.__name__, ctime()))
func()
return wrappedfunc
@timefun
def foo():
print("I am foo")
foo()
sleep(2)
foo()
上面代码理解装饰器执行行为可理解成
foo = timefun(foo)
#foo先作为参数赋值给func后,foo接收指向timefun返回的wrappedfunc
foo()
#调用foo(),即等价调用wrappedfunc()
#内部函数wrappedfunc被引用,所以外部函数的func变量(自由变量)并没有释放
#func里保存的是原foo函数对象
[if !supportLists]1.8.4.2 [endif]例2:被装饰的函数有参数
from time import ctime, sleep
def timefun(func):
def wrappedfunc(a, b):
print("%s called at %s"%(func.__name__, ctime()))
print(a, b)
func(a, b)
return wrappedfunc
@timefun
def foo(a, b):
print(a+b)
foo(3,5)
sleep(2)
foo(2,4)
[if !supportLists]1.8.4.3 [endif]例3:被装饰的函数有不定长参数
from time import ctime, sleep
def timefun(func):
def wrappedfunc(*args, **kwargs):
print("%s called at %s"%(func.__name__, ctime()))
func(*args, **kwargs)
return wrappedfunc
@timefun
def foo(a, b, c):
print(a+b+c)
foo(3,5,7)
sleep(2)
foo(2,4,9)
[if !supportLists]1.8.4.4 [endif]例4:装饰器中的return
from time import ctime, sleep
def timefun(func):
def wrappedfunc():
print("%s called at %s"%(func.__name__, ctime()))
func()
return wrappedfunc
@timefun
def foo():
print("I am foo")
@timefun
def getInfo():
return '----hahah---'
foo()
sleep(2)
foo()
print(getInfo())
执行结果:
foo called at Fri Nov 4 21:55:35 2016
I am foo
foo called at Fri Nov 4 21:55:37 2016
I am foo
getInfo called at Fri Nov 4 21:55:37 2016
None
如果修改装饰器为return func(),则运行结果:
foo called at Fri Nov 4 21:55:57 2016
I am foo
foo called at Fri Nov 4 21:55:59 2016
I am foo
getInfo called at Fri Nov 4 21:55:59 2016
----hahah---
总结:
[if !supportLists]· [endif]一般情况下为了让装饰器更通用,可以有return
[if !supportLists]1.8.4.5 [endif]例5:装饰器带参数,在原有装饰器的基础上,设置外部变量
#decorator2.py
from time import ctime, sleep
def timefun_arg(pre="hello"):
def timefun(func):
def wrappedfunc():
print("%s called at %s %s"%(func.__name__, ctime(), pre))
return func()
return wrappedfunc
return timefun
@timefun_arg("wangcai")
def foo():
print("I am foo")
@timefun_arg("python")
def too():
print("I am too")
foo()
sleep(2)
foo()
too()
sleep(2)
too()
可以理解为
foo()==timefun_arg("wangcai")(foo)()
[if !supportLists]1.8.4.6 [endif]例6:类装饰器(扩展,非重点)
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写(overide)了 __call__() 方法,那么这个对象就是callable的。
class Test():
def __call__(self):
print('call me!')
t = Test()
t() # call me
类装饰器demo
class Test(object):
def __init__(self, func):
print("---初始化---")
print("func name is %s"%func.__name__)
self.__func = func
def __call__(self):
print("---装饰器中的功能---")
self.__func()
#说明:
#1.当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象
#并且会把test这个函数名当做参数传递到__init__方法中
#即在__init__方法中的func变量指向了test函数体
#
#2. test函数相当于指向了用Test创建出来的实例对象
#
#3.当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法
#
#4.为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用
#所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体
@Test
def test():
print("----test---")
test()
showpy()#如果把这句话注释,重新运行程序,依然会看到"--初始化--"
运行结果如下:
---初始化---
func name is test
---装饰器中的功能---
----test---
[if !supportLists]1.9 [endif]python是动态语言
[if !supportLists]1.9.1 [endif]动态语言的定义
动态编程语言是高级程序设计语言的一个类别,在计算机科学领域已被广泛应用。它是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言目前非常具有活力。例如JavaScript便是一个动态语言,除此之外如 PHP 、 Ruby 、 Python 等也都属于动态语言,而 C 、 C++ 等语言则不属于动态语言。 ----来自维基百科
[if !supportLists]1.9.2 [endif]运行的过程中给对象绑定(添加)属性
>>> class Person(object):
def __init__(self, name = None, age = None):
self.name = name
self.age = age
>>> P = Person("小明", "24")
>>>
在这里,我们定义了1个类Person,在这个类里,定义了两个初始属性name和age,但是人还有性别啊!如果这个类不是你写的是不是你会尝试访问性别这个属性呢?
>>> P.sex = "male"
>>> P.sex
'male'
>>>
这时候就发现问题了,我们定义的类里面没有sex这个属性啊!怎么回事呢? 这就是动态语言的魅力和坑! 这里 实际上就是 动态给实例绑定属性!
[if !supportLists]1.9.3 [endif]运行的过程中给类绑定(添加)属性
>>> P1 = Person("小丽", "25")
>>> P1.sex
Traceback (most recent call last):
File "", line 1, in
P1.sex
AttributeError: Person instance has no attribute 'sex'
>>>
我们尝试打印P1.sex,发现报错,P1没有sex这个属性!---- 给P这个实例绑定属性对P1这个实例不起作用! 那我们要给所有的Person的实例加上 sex属性怎么办呢? 答案就是直接给Person绑定属性!
>>>> Person.sex = None #给类Person添加一个属性
>>> P1 = Person("小丽", "25")
>>> print(P1.sex) #如果P1这个实例对象中没有sex属性的话,那么就会访问它的类属性
None #可以看到没有出现异常
>>>
[if !supportLists]1.9.4 [endif]运行的过程中给类绑定(添加)方法
我们直接给Person绑定sex这个属性,重新实例化P1后,P1就有sex这个属性了! 那么function呢?怎么绑定?
>>> class Person(object):
def __init__(self, name = None, age = None):
self.name = name
self.age = age
def eat(self):
print("eat food")
>>> def run(self, speed):
print("%s在移动, 速度是 %d km/h"%(self.name, speed))
>>> P = Person("老王", 24)
>>> P.eat()
eat food
>>>
>>> P.run()
Traceback (most recent call last):
File "", line 1, in
P.run()
AttributeError: Person instance has no attribute 'run'
>>>
>>>
>>> import types
>>> P.run = types.MethodType(run, P)
>>> P.run(180)
老王在移动,速度是 180 km/h
既然给类添加方法,是使用类名.方法名 = xxxx,那么给对象添加一个方法也是类似的对象.方法名 = xxxx
完整的代码如下:
import types
#定义了一个类
class Person(object):
num = 0
def __init__(self, name = None, age = None):
self.name = name
self.age = age
def eat(self):
print("eat food")
#定义一个实例方法
def run(self, speed):
print("%s在移动, 速度是 %d km/h"%(self.name, speed))
#定义一个类方法
@classmethod
def testClass(cls):
cls.num = 100
#定义一个静态方法
@staticmethod
def testStatic():
print("---static method----")
#创建一个实例对象
P = Person("老王", 24)
#调用在class中的方法
P.eat()
#给这个对象添加实例方法
P.run = types.MethodType(run, P)
#调用实例方法
P.run(180)
#给Person类绑定类方法
Person.testClass = testClass
#调用类方法
print(Person.num)
Person.testClass()
print(Person.num)
#给Person类绑定静态方法
Person.testStatic = testStatic
#调用静态方法
Person.testStatic()
[if !supportLists]1.9.5 [endif]运行的过程中删除属性、方法
删除的方法:
[if !supportLists]1. [endif]del对象.属性名
[if !supportLists]2. [endif]delattr(对象, "属性名")
通过以上例子可以得出一个结论:相对于动态语言,静态语言具有严谨性!所以,玩动态语言的时候,小心动态的坑!
那么怎么避免这种情况呢?请使用__slots__,
[if !supportLists]1.9.6 [endif]__slots__
现在我们终于明白了,动态语言与静态语言的不同
动态语言:可以在运行的过程中,修改代码
静态语言:编译时已经确定好代码,运行过程中不能修改
如果我们想要限制实例的属性怎么办?比如,只允许对Person实例添加name和age属性。只能限定实例对象的添加属性和方法
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
>>> class Person(object):
__slots__ = ("name", "age")
>>> P = Person()
>>> P.name = "老王"
>>> P.age = 20
>>> P.score = 100
Traceback (most recent call last):
File "", line 1, in
AttributeError: Person instance has no attribute 'score'
>>>
注意:
[if !supportLists]· [endif]使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
In [67]: class Test(Person):
...: pass
...:
In [68]: t = Test()
In [69]: t.score = 100
[if !supportLists]1.10 [endif]元类(了解)
[if !supportLists]1.10.1 [endif]类也是对象
在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:
>>> class ObjectCreator(object):
… pass
…
>>> my_object = ObjectCreator()
>>> print my_object
<__main__.ObjectCreator object at 0x8974f2c>
但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。
下面的代码段:
>>> class ObjectCreator(object):
… pass
…
将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类对象ObjectCreator)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:
[if !supportLists]1. [endif]你可以将它赋值给一个变量
[if !supportLists]2. [endif]你可以拷贝它
[if !supportLists]3. [endif]你可以为它增加属性
[if !supportLists]4. [endif]你可以将它作为函数参数进行传递
下面是示例:
>>> print ObjectCreator #你可以打印一个类,因为它其实也是一个对象
>>> def echo(o):
… print o
…
>>> echo(ObjectCreator) #你可以将类做为参数传给函数
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' #你可以为类增加属性
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator #你可以将类赋值给一个变量
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>
[if !supportLists]1.10.2 [endif]动态地创建类
因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可。
>>> def choose_class(name):
… if name == 'foo':
… class Foo(object):
… pass
… return Foo #返回的是类,不是类的实例
… else:
… class Bar(object):
… pass
… return Bar
…
>>> MyClass = choose_class('foo')
>>> print MyClass #函数返回的是类,不是类的实例
>>> print MyClass() #你可以通过这个类创建类实例,也就是对象
<__main__.Foo object at 0x89c6d4c>
但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。
还记得内建函数type吗?这个古老但强大的函数能够让你知道一个对象的类型是什么,就像这样:
>>> print type(1) #数值的类型
>>> print type("1") #字符串的类型
>>> print type(ObjectCreator()) #实例对象的类型
>>> print type(ObjectCreator) #类的类型
仔细观察上面的运行结果,发现使用type对ObjectCreator查看类型是,答案为type, 是不是有些惊讶。。。看下面
[if !supportLists]1.10.3 [endif]使用type创建类
type还有一种完全不同的功能,动态的创建类。
type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)
type可以像这样工作:
type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
比如下面的代码:
In [2]: class Test: #定义了一个Test类
...: pass
...:
In [3]: Test() #创建了一个Test类的实例对象
Out[3]: <__main__.Test at 0x10d3f8438>
可以手动像这样创建:
Test2 = type("Test2",(),{}) #定了一个Test2类
In [5]: Test2() #创建了一个Test2类的实例对象
Out[5]: <__main__.Test2 at 0x10d406b38>
我们使用"Test2"作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。即type函数中第1个实参,也可以叫做其他的名字,这个名字表示类的名字
In [23]: MyDogClass = type('MyDog', (), {})
In [24]: print MyDogClass
使用help来测试这2个类
In [10]: help(Test) #用help查看Test类
Help on class Test in module __main__:
class Test(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
In [8]: help(Test2) #用help查看Test2类
Help on class Test2 in module __main__:
class Test2(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
[if !supportLists]1.10.4 [endif]使用type创建带有属性的类
type接受一个字典来为类定义属性,因此
>>> Foo = type('Foo', (), {'bar':True})
可以翻译为:
>>> class Foo(object):
… bar = True
并且可以将Foo当成一个普通的类一样使用:
>>> print Foo
>>> print Foo.bar
True
>>> f = Foo()
>>> print f
<__main__.Foo object at 0x8a9b84c>
>>> print f.bar
True
当然,你可以向这个类继承,所以,如下的代码:
>>> class FooChild(Foo):
… pass
就可以写成:
>>> FooChild = type('FooChild', (Foo,),{})
>>> print FooChild
>>> print FooChild.bar # bar属性是由Foo继承而来
True
注意:
[if !supportLists]· [endif]type的第2个参数,元组中是父类的名字,而不是字符串
[if !supportLists]· [endif]添加的属性是类属性,并不是实例属性
[if !supportLists]1.10.5 [endif]使用type创建带有方法的类
最终你会希望为你的类增加方法。只需要定义一个有着恰当签名的函数并将其作为属性赋值就可以了。
添加实例方法
In [46]: def echo_bar(self): #定义了一个普通的函数
...: print(self.bar)
...:
In [47]: FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) #让FooChild类中的echo_bar属性,指向了上面定义的函数
In [48]: hasattr(Foo, 'echo_bar') #判断Foo类中,是否有echo_bar这个属性
Out[48]: False
In [49]:
In [49]: hasattr(FooChild, 'echo_bar') #判断FooChild类中,是否有echo_bar这个属性
Out[49]: True
In [50]: my_foo = FooChild()
In [51]: my_foo.echo_bar()
True
添加静态方法
In [36]: @staticmethod
...: def testStatic():
...: print("static method ....")
...:
In [37]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic":
...: testStatic})
In [38]: fooclid = Foochild()
In [39]: fooclid.testStatic
Out[39]:
In [40]: fooclid.testStatic()
static method ....
In [41]: fooclid.echo_bar()
True
添加类方法
In [42]: @classmethod
...: def testClass(cls):
...: print(cls.bar)
...:
In [43]:
In [43]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic":
...: testStatic, "testClass":testClass})
In [44]:
In [44]: fooclid = Foochild()
In [45]: fooclid.testClass()
True
你可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。
[if !supportLists]1.10.6 [endif]到底什么是元类
元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。
元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:
MyClass = MetaClass() #使用元类创建出一个对象,这个对象称为“类”
MyObject = MyClass() #使用“类”来创建出实例对象
你已经看到了type可以让你像这样做:
MyClass = type('MyClass', (), {})
这是因为函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查__class__属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。
>>> age = 35
>>> age.__class__
>>> name = 'bob'
>>> name.__class__
>>> def foo(): pass
>>>foo.__class__
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
现在,对于任何一个__class__的__class__属性又是什么呢?
>>> a.__class__.__class__
>>> age.__class__.__class__
>>> foo.__class__.__class__
>>> b.__class__.__class__
因此,元类就是创建类这种对象的东西。type就是Python的内建元类,当然了,你也可以创建自己的元类。
[if !supportLists]1.10.7 [endif]__metaclass__属性
你可以在定义一个类的时候为其添加__metaclass__属性。
class Foo(object):
__metaclass__ = something…
...省略...
如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。把下面这段话反复读几次。当你写如下代码时 :
class Foo(Bar):
pass
Python做了如下的操作:
[if !supportLists]1. [endif]Foo中有__metaclass__这个属性吗?如果是,Python会通过__metaclass__创建一个名字为Foo的类(对象)
[if !supportLists]2. [endif]如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。
[if !supportLists]3. [endif]如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
[if !supportLists]4. [endif]如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。
现在的问题就是,你可以在__metaclass__中放置些什么代码呢?答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东东都可以。
[if !supportLists]1.10.8 [endif]自定义元类
元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。
假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。
幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。
python2中
#-*- coding:utf-8 -*-
def upper_attr(future_class_name, future_class_parents, future_class_attr):
#遍历属性字典,把不是__开头的属性名字变为大写
newAttr = {}
for name,value in future_class_attr.items():
if not name.startswith("__"):
newAttr[name.upper()] = value
#调用type来创建一个类
return type(future_class_name, future_class_parents, newAttr)
class Foo(object):
__metaclass__ = upper_attr #设置Foo类的元类为upper_attr
bar = 'bip'
print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))
f = Foo()
print(f.BAR)
python3中
#-*- coding:utf-8 -*-
def upper_attr(future_class_name, future_class_parents, future_class_attr):
#遍历属性字典,把不是__开头的属性名字变为大写
newAttr = {}
for name,value in future_class_attr.items():
if not name.startswith("__"):
newAttr[name.upper()] = value
#调用type来创建一个类
return type(future_class_name, future_class_parents, newAttr)
class Foo(object, metaclass=upper_attr):
bar = 'bip'
print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))
f = Foo()
print(f.BAR)
现在让我们再做一次,这一次用一个真正的class来当做元类。
#coding=utf-8
class UpperAttrMetaClass(type):
# __new__是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回之的方法
#而__init__只是用来将传入的参数初始化给对象
#你很少用到__new__,除非你希望能够控制对象的创建
#这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
#如果你希望的话,你也可以在__init__中做些事情
#还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def __new__(cls, future_class_name, future_class_parents, future_class_attr):
#遍历属性字典,把不是__开头的属性名字变为大写
newAttr = {}
for name,value in future_class_attr.items():
if not name.startswith("__"):
newAttr[name.upper()] = value
#方法1:通过'type'来做类对象的创建
# return type(future_class_name, future_class_parents, newAttr)
#方法2:复用type.__new__方法
#这就是基本的OOP编程,没什么魔法
# return type.__new__(cls, future_class_name, future_class_parents, newAttr)
#方法3:使用super方法
return super(UpperAttrMetaClass, cls).__new__(cls, future_class_name, future_class_parents, newAttr)
#python2的用法
class Foo(object):
__metaclass__ = UpperAttrMetaClass
bar = 'bip'
# python3的用法
# class Foo(object, metaclass = UpperAttrMetaClass):
# bar = 'bip'
print(hasattr(Foo, 'bar'))
#输出: False
print(hasattr(Foo, 'BAR'))
#输出:True
f = Foo()
print(f.BAR)
#输出:'bip'
就是这样,除此之外,关于元类真的没有别的可说的了。但就元类本身而言,它们其实是很简单的:
[if !supportLists]1. [endif]拦截类的创建
[if !supportLists]2. [endif]修改类
[if !supportLists]3. [endif]返回修改之后的类
[if !supportLists]1.10.9 [endif]究竟为什么要使用元类?
现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?好吧,一般来说,你根本就用不上它:
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters
[if !supportLists]1.11 [endif]垃圾回收(了解)
[if !supportLists]1.11.1 [endif]小整数对象池
整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。
Python对小整数的定义是 [-5, 257) 这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象.
同理,单个字母也是这样的。
但是当定义2个相同的字符串时,引用计数为0,触发垃圾回收
[if !supportLists]1.11.2 [endif]大整数对象池
每一个大整数,均创建一个新的对象。
[if !supportLists]1.11.3 [endif]intern机制
a1 = "HelloWorld"
a2 = "HelloWorld"
a3 = "HelloWorld"
a4 = "HelloWorld"
a5 = "HelloWorld"
a6 = "HelloWorld"
a7 = "HelloWorld"
a8 = "HelloWorld"
a9 = "HelloWorld"
python会不会创建9个对象呢?在内存中会不会开辟9个”HelloWorld”的内存空间呢? 想一下,如果是这样的话,我们写10000个对象,比如a1=”HelloWorld”…..a1000=”HelloWorld”, 那他岂不是开辟了1000个”HelloWorld”所占的内存空间了呢?如果真这样,内存不就爆了吗?所以python中有这样一个机制——intern机制,让他只占用一个”HelloWorld”所占的内存空间。靠引用计数去维护何时释放。
总结
[if !supportLists]· [endif]小整数[-5,257)共用对象,常驻内存
[if !supportLists]· [endif]单个字符共用对象,常驻内存
[if !supportLists]· [endif]单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
字符串(含有空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁
大整数不共用内存,引用计数为0,销毁
数值类型和字符串类型在Python中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象
[if !supportLists]1.11.4 [endif]Garbage collection(GC垃圾回收)
现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。 对于一个字符串、列表、类甚至数值都是对象,且定位简单易用的语言,自然不会让用户去处理如何分配回收内存的问题。 python里也同java一样采用了垃圾收集机制,不过不一样的是: python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略
引用计数机制:
python里每一个东西都是对象,它们的核心就是一个结构体:PyObject
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少
#define Py_INCREF(op) ((op)->ob_refcnt++) //增加计数
#define Py_DECREF(op) \ //减少计数
if (--(op)->ob_refcnt != 0) \
; \
else \
__Py_Dealloc((PyObject *)(op))
当引用计数为0时,该对象生命就结束了。
引用计数机制的优点:
[if !supportLists]· [endif]简单
[if !supportLists]· [endif]实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
[if !supportLists]· [endif]维护引用计数消耗资源
[if !supportLists]· [endif]循环引用
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
list1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。 对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制。(标记清除和分代收集)
[if !supportLists]1.11.5 [endif]话说Ruby与 Python 垃圾回收
英文原文: visualizing garbage collection in ruby and python
[if !supportLists]1.11.5.1 [endif]应用程序那颗跃动的心
GC系统所承担的工作远比"垃圾回收"多得多。实际上,它们负责三个重要任务。它们
[if !supportLists]· [endif]为新生成的对象分配内存
[if !supportLists]· [endif]识别那些垃圾对象,并且
[if !supportLists]· [endif]从垃圾对象那回收内存。
如果将应用程序比作人的身体:所有你所写的那些优雅的代码,业务逻辑,算法,应该就是大脑。以此类推,垃圾回收机制应该是那个身体器官呢?(我从RuPy听众那听到了不少有趣的答案:腰子、白血球 :) )
我认为垃圾回收就是应用程序那颗跃动的心。像心脏为身体其他器官提供血液和营养物那样,垃圾回收器为你的应该程序提供内存和对象。如果心脏停跳,过不了几秒钟人就完了。如果垃圾回收器停止工作或运行迟缓,像动脉阻塞,你的应用程序效率也会下降,直至最终死掉。
[if !supportLists]1.11.5.2 [endif]一个简单的例子
运用实例一贯有助于理论的理解。下面是一个简单类,分别用Python和Ruby写成,我们今天就以此为例:
顺便提一句,两种语言的代码竟能如此相像:Ruby和 Python 在表达同一事物上真的只是略有不同。但是在这两种语言的内部实现上是否也如此相似呢?
顺便提一句,两种语言的代码竟能如此相像:Ruby和 Python 在表达同一事物上真的只是略有不同。但是在这两种语言的内部实现上是否也如此相似呢?
[if !supportLists]1.11.5.3 [endif]Ruby的对象分配
当我们执行上面的Node.new(1)时,Ruby到底做了什么?Ruby是如何为我们创建新的对象的呢? 出乎意料的是它做的非常少。实际上,早在代码开始执行前,Ruby就提前创建了成百上千个对象,并把它们串在链表上,名曰:可用列表。下图所示为可用列表的概念图:
想象一下每个白色方格上都标着一个"未使用预创建对象"。当我们调用 Node.new ,Ruby只需取一个预创建对象给我们使用即可:
上图中左侧灰格表示我们代码中使用的当前对象,同时其他白格是未使用对象。(请注意:无疑我的示意图是对实际的简化。实际上,Ruby会用另一个对象来装载字符串"ABC",另一个对象装载Node类定义,还有一个对象装载了代码中分析出的抽象语法树,等等)
如果我们再次调用Node.new,Ruby将递给我们另一个对象:
这个简单的用链表来预分配对象的算法已经发明了超过50年,而发明人这是赫赫有名的计算机科学家John McCarthy,一开始是用Lisp实现的。Lisp不仅是最早的函数式编程语言,在计算机科学领域也有许多创举。其一就是利用垃圾回收机制自动化进行程序内存管理的概念。
标准版的Ruby,也就是众所周知的"Matz's Ruby Interpreter"(MRI),所使用的GC算法与McCarthy在1960年的实现方式很类似。无论好坏,Ruby的垃圾回收机制已经53岁高龄了。像Lisp一样,Ruby预先创建一些对象,然后在你分配新对象或者变量的时候供你使用。
[if !supportLists]1.11.5.4 [endif]Python的对象分配
我们已经了解了Ruby预先创建对象并将它们存放在可用列表中。那Python又怎么样呢?
尽管由于许多原因Python也使用可用列表(用来回收一些特定对象比如 list),但在为新对象和变量分配内存的方面Python和Ruby是不同的。
例如我们用Pyhon来创建一个Node对象:
与Ruby不同,当创建对象时Python立即向操作系统请求内存。(Python实际上实现了一套自己的内存分配系统,在操作系统堆之上提供了一个抽象层。但是我今天不展开说了。)
当我们创建第二个对象的时候,再次像OS请求内存:
看起来够简单吧,在我们创建对象的时候,Python会花些时间为我们找到并分配内存。
[if !supportLists]1.11.5.5 [endif]Ruby开发者住在凌乱的房间里
Ruby把无用的对象留在内存里,直到下一次GC执行
回过来看Ruby。随着我们创建越来越多的对象,Ruby会持续寻可用列表里取预创建对象给我们。因此,可用列表会逐渐变短:
...然后更短:
请注意我一直在为变量n1赋新值,Ruby把旧值留在原处。"ABC","JKL"和"MNO"三个Node实例还滞留在内存中。Ruby不会立即清除代码中不再使用的旧对象!Ruby开发者们就像是住在一间凌乱的房间,地板上摞着衣服,要么洗碗池里都是脏盘子。作为一个Ruby程序员,无用的垃圾对象会一直环绕着你。
[if !supportLists]1.11.5.6 [endif]Python开发者住在卫生之家庭
用完的垃圾对象会立即被Python打扫干净
Python与Ruby的垃圾回收机制颇为不同。让我们回到前面提到的三个Python Node对象:
在内部,创建一个对象时,Python总是在对象的C结构体里保存一个整数,称为 引用数。期初,Python将这个值设置为1:
值为1说明分别有个一个指针指向或是引用这三个对象。假如我们现在创建一个新的Node实例,JKL:
与之前一样,Python设置JKL的引用数为1。然而,请注意由于我们改变了n1指向了JKL,不再指向ABC,Python就把ABC的引用数置为0了。 此刻,Python垃圾回收器立刻挺身而出!每当对象的引用数减为0,Python立即将其释放,把内存还给操作系统:
上面Python回收了ABC Node实例使用的内存。记住,Ruby弃旧对象原地于不顾,也不释放它们的内存。
Python的这种垃圾回收算法被称为引用计数。是George-Collins在1960年发明的,恰巧与John McCarthy发明的可用列表算法在同一年出现。就像Mike-Bernstein在6月份哥谭市Ruby大会杰出的垃圾回收机制演讲中说的: "1960年是垃圾收集器的黄金年代..."
Python开发者工作在卫生之家,你可以想象,有个患有轻度OCD(一种强迫症)的室友一刻不停地跟在你身后打扫,你一放下脏碟子或杯子,有个家伙已经准备好把它放进洗碗机了!
现在来看第二例子。加入我们让n2引用n1:
上图中左边的DEF的引用数已经被Python减少了,垃圾回收器会立即回收DEF实例。同时JKL的引用数已经变为了2 ,因为n1和n2都指向它。
[if !supportLists]1.11.5.7 [endif]标记-清除
最终那间凌乱的房间充斥着垃圾,再不能岁月静好了。在Ruby程序运行了一阵子以后,可用列表最终被用光光了:
此刻所有Ruby预创建对象都被程序用过了(它们都变灰了),可用列表里空空如也(没有白格子了)。
此刻Ruby祭出另一McCarthy发明的算法,名曰:标记-清除。首先Ruby把程序停下来,Ruby用"地球停转垃圾回收大法"。之后Ruby轮询所有指针,变量和代码产生别的引用对象和其他值。同时Ruby通过自身的虚拟机便利内部指针。标记出这些指针引用的每个对象。我在图中使用M表示。
上图中那三个被标M的对象是程序还在使用的。在内部,Ruby实际上使用一串位值,被称为:可用位图(译注:还记得《编程珠玑》里的为突发排序吗,这对离散度不高的有限整数集合具有很强的压缩效果,用以节约机器的资源。),来跟踪对象是否被标记了。
如果说被标记的对象是存活的,剩下的未被标记的对象只能是垃圾,这意味着我们的代码不再会使用它了。我会在下图中用白格子表示垃圾对象:
接下来Ruby清除这些无用的垃圾对象,把它们送回到可用列表中:
在内部这一切发生得迅雷不及掩耳,因为Ruby实际上不会吧对象从这拷贝到那。而是通过调整内部指针,将其指向一个新链表的方式,来将垃圾对象归位到可用列表中的。
现在等到下回再创建对象的时候Ruby又可以把这些垃圾对象分给我们使用了。在Ruby里,对象们六道轮回,转世投胎,享受多次人生。
[if !supportLists]1.11.5.8 [endif]标记-删除 vs. 引用计数
乍一看,Python的GC算法貌似远胜于Ruby的:宁舍洁宇而居秽室乎?为什么Ruby宁愿定期强制程序停止运行,也不使用Python的算法呢?
然而,引用计数并不像第一眼看上去那样简单。有许多原因使得不许多语言不像Python这样使用引用计数GC算法:
首先,它不好实现。Python不得不在每个对象内部留一些空间来处理引用数。这样付出了一小点儿空间上的代价。但更糟糕的是,每个简单的操作(像修改变量或引用)都会变成一个更复杂的操作,因为Python需要增加一个计数,减少另一个,还可能释放对象。
第二点,它相对较慢。虽然Python随着程序执行GC很稳健(一把脏碟子放在洗碗盆里就开始洗啦),但这并不一定更快。Python不停地更新着众多引用数值。特别是当你不再使用一个大数据结构的时候,比如一个包含很多元素的列表,Python可能必须一次性释放大量对象。减少引用数就成了一项复杂的递归过程了。
最后,它不是总奏效的。引用计数不能处理环形数据结构--也就是含有循环引用的数据结构。
[if !supportLists]1.11.6 [endif]Python中的循环数据结构以及引用计数
[if !supportLists]1.11.6.1 [endif]循环引用
通过上篇,我们知道在Python中,每个对象都保存了一个称为引用计数的整数值,来追踪到底有多少引用指向了这个对象。无论何时,如果我们程序中的一个变量或其他对象引用了目标对象,Python将会增加这个计数值,而当程序停止使用这个对象,则Python会减少这个计数值。一旦计数值被减到零,Python将会释放这个对象以及回收相关内存空间。
从六十年代开始,计算机科学界就面临了一个严重的理论问题,那就是针对引用计数这种算法来说,如果一个数据结构引用了它自身,即如果这个数据结构是一个循环数据结构,那么某些引用计数值是肯定无法变成零的。为了更好地理解这个问题,让我们举个例子。下面的代码展示了一些上周我们所用到的节点类:
我们有一个"构造器"(在Python中叫做 __init__ ),在一个实例变量中存储一个单独的属性。在类定义之后我们创建两个节点,ABC以及DEF,在图中为左边的矩形框。两个节点的引用计数都被初始化为1,因为各有两个引用指向各个节点(n1和n2)。
现在,让我们在节点中定义两个附加的属性,next以及prev:
跟Ruby不同的是,Python中你可以在代码运行的时候动态定义实例变量或对象属性。这看起来似乎有点像Ruby缺失了某些有趣的魔法。(声明下我不是一个Python程序员,所以可能会存在一些命名方面的错误)。我们设置 n1.next 指向 n2,同时设置 n2.prev 指回 n1。现在,我们的两个节点使用循环引用的方式构成了一个双向链表。同时请注意到ABC以及 DEF 的引用计数值已经增加到了2。这里有两个指针指向了每个节点:首先是 n1 以及 n2,其次就是 next 以及 prev。
现在,假定我们的程序不再使用这两个节点了,我们将n1和 n2 都设置为null(Python中是None)。
好了,Python会像往常一样将每个节点的引用计数减少到1。
[if !supportLists]1.11.6.2 [endif]在Python中的零代(Generation Zero)
请注意在以上刚刚说到的例子中,我们以一个不是很常见的情况结尾:我们有一个“孤岛”或是一组未使用的、互相指向的对象,但是谁都没有外部引用。换句话说,我们的程序不再使用这些节点对象了,所以我们希望Python的垃圾回收机制能够足够智能去释放这些对象并回收它们占用的内存空间。但是这不可能,因为所有的引用计数都是1而不是0。Python的引用计数算法不能够处理互相指向自己的对象。
这就是为什么Python要引入Generational GC算法的原因!正如Ruby使用一个链表(free list)来持续追踪未使用的、自由的对象一样,Python使用一种不同的链表来持续追踪活跃的对象。而不将其称之为“活跃列表”,Python的内部C代码将其称为零代(Generation Zero)。每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表:
从上边可以看到当我们创建ABC节点的时候,Python将其加入零代链表。请注意到这并不是一个真正的列表,并不能直接在你的代码中访问,事实上这个链表是一个完全内部的Python运行时。 相似的,当我们创建DEF节点的时候,Python将其加入同样的链表:
现在零代包含了两个节点对象。(他还将包含Python创建的每个其他值,与一些Python自己使用的内部值。)
[if !supportLists]1.11.6.3 [endif]检测循环引用
随后,Python会循环遍历零代列表上的每个对象,检查列表中每个互相引用的对象,根据规则减掉其引用计数。在这个过程中,Python会一个接一个的统计内部引用的数量以防过早地释放对象。
为了便于理解,来看一个例子:
从上面可以看到ABC和 DEF 节点包含的引用数为1.有三个其他的对象同时存在于零代链表中,蓝色的箭头指示了有一些对象正在被零代链表之外的其他对象所引用。(接下来我们会看到,Python中同时存在另外两个分别被称为一代和二代的链表)。这些对象有着更高的引用计数因为它们正在被其他指针所指向着。
接下来你会看到Python的GC是如何处理零代链表的。
通过识别内部引用,Python能够减少许多零代链表对象的引用计数。在上图的第一行中你能够看见ABC和DEF的引用计数已经变为零了,这意味着收集器可以释放它们并回收内存空间了。剩下的活跃的对象则被移动到一个新的链表:一代链表。
从某种意义上说,Python的GC算法类似于Ruby所用的标记回收算法。周期性地从一个对象到另一个对象追踪引用以确定对象是否还是活跃的,正在被程序所使用的,这正类似于Ruby的标记过程。
[if !supportLists]1.11.6.4 [endif]Python中的GC阈值
Python什么时候会进行这个标记过程?随着你的程序运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。从理论上说,这两个值应该保持一致,因为程序新建的每个对象都应该最终被释放掉。
当然,事实并非如此。因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。
随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。
通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。
弱代假说
来看看代垃圾回收算法的核心行为:垃圾回收器会更频繁的处理新对象。一个新的对象即是你的程序刚刚创建的,而一个来的对象则是经过了几个时间周期之后仍然存在的对象。Python会在当一个对象从零代移动到一代,或是从一代移动到二代的过程中提升(promote)这个对象。
为什么要这么做?这种算法的根源来自于弱代假说(weak generational hypothesis)。这个假说由两个观点构成:首先是年亲的对象通常死得也快,而老对象则很有可能存活更长的时间。
假定现在我用Python或是Ruby创建一个新对象:
根据假说,我的代码很可能仅仅会使用ABC很短的时间。这个对象也许仅仅只是一个方法中的中间结果,并且随着方法的返回这个对象就将变成垃圾了。大部分的新对象都是如此般地很快变成垃圾。然而,偶尔程序会创建一些很重要的,存活时间比较长的对象-例如web应用中的session变量或是配置项。
通过频繁的处理零代链表中的新对象,Python的垃圾收集器将把时间花在更有意义的地方:它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足阈值的条件,收集器才回去处理那些老变量。
[if !supportLists]1.11.7 [endif]gc模块
[if !supportLists]1.11.7.1 [endif]垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅。
[if !supportLists]1.11.7.1.1 [endif]导致引用计数+1的情况
[if !supportLists]· [endif]对象被创建,例如a=23
[if !supportLists]· [endif]对象被引用,例如b=a
[if !supportLists]· [endif]对象被作为参数,传入到一个函数中,例如func(a)
[if !supportLists]· [endif]对象作为一个元素,存储在容器中,例如list1=[a,a]
[if !supportLists]1.11.7.1.2 [endif]导致引用计数-1的情况
[if !supportLists]· [endif]对象的别名被显式销毁,例如del a
[if !supportLists]· [endif]对象的别名被赋予新的对象,例如a=24
[if !supportLists]· [endif]一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
[if !supportLists]· [endif]对象所在的容器被销毁,或从容器中删除对象
[if !supportLists]1.11.7.1.3 [endif]查看一个对象的引用计数
import sys
a = "hello world"
sys.getrefcount(a)
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
[if !supportLists]1.11.7.2 [endif]循环引用导致内存泄露
引用计数的缺陷是循环引用的问题
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
def f2():
while True:
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
del c1
del c2
#把python的gc关闭
gc.disable()
f2()
执行f2(),进程占用的内存会不断增大。
[if !supportLists]· [endif]创建了c1,c2后这两块内存的引用计数都是1,执行c1.t=c2和c2.t=c1后,这两块内存的引用计数变成2.
[if !supportLists]· [endif]在del c1后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del c2后,同理,内存1的对象,内存2的对象的引用数都是1。
[if !supportLists]· [endif]虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。
[if !supportLists]1.11.7.3 [endif]垃圾回收
#coding=utf-8
import gc
class ClassA():
def __init__(self):
print('object born,id:%s'%str(hex(id(self))))
# def __del__(self):
# print('object del,id:%s'%str(hex(id(self))))
def f3():
print("-----0------")
# print(gc.collect())
c1 = ClassA()
c2 = ClassA()
c1.t = c2
c2.t = c1
print("-----1------")
del c1
del c2
print("-----2------")
print(gc.garbage)
print("-----3------")
print(gc.collect()) #显式执行垃圾回收
print("-----4------")
print(gc.garbage)
print("-----5------")
if __name__ == '__main__':
gc.set_debug(gc.DEBUG_LEAK) #设置gc模块的日志
f3()
python2运行结果:
-----0------
object born,id:0x724b20
object born,id:0x724b48
-----1------
-----2------
[]
-----3------
gc: collectable
gc: collectable
gc: collectable
gc: collectable
4
-----4------
[<__main__.ClassA instance at 0x724b20>, <__main__.ClassA instance at 0x724b48>, {'t': <__main__.ClassA instance at 0x724b48>}, {'t': <__main__.ClassA instance at 0x724b20>}]
-----5------
说明:
[if !supportLists]· [endif]垃圾回收后的对象会放在gc.garbage列表里面
[if !supportLists]· [endif]gc.collect()会返回不可达的对象数目,4等于两个对象以及它们对应的dict
有三种情况会触发垃圾回收:
[if !supportLists]1. [endif]调用gc.collect(),
[if !supportLists]2. [endif]当gc模块的计数器达到阀值的时候。
[if !supportLists]3. [endif]程序退出的时候
[if !supportLists]1.11.7.4 [endif]gc模块常用功能解析
gc模块提供一个接口给开发者设置垃圾回收的选项。上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。
[if !supportLists]1.11.7.4.1 [endif]常用函数:
1、gc.set_debug(flags) 设置gc的debug日志,一般设置为gc.DEBUG_LEAK
2、gc.collect([generation]) 显式进行垃圾回收,可以输入参数,0代表只检查第一代的对象,1代表检查一,二代的对象,2代表检查一,二,三代的对象,如果不传参数,执行一个full collection,也就是等于传2。 返回不可达(unreachable objects)对象的数目
3、gc.get_threshold() 获取的gc模块中自动执行垃圾回收的频率。
4、gc.set_threshold(threshold0[, threshold1[, threshold2]) 设置自动执行垃圾回收的频率。
5、gc.get_count() 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
[if !supportLists]1.11.7.4.2 [endif]gc模块的自动垃圾回收机制
必须要import gc模块,并且is_enable()=True才会启动自动垃圾回收。
这个机制的主要作用就是发现并处理不可达的垃圾对象。
垃圾回收=垃圾检查+垃圾回收
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:
print gc.get_count() # (590, 8, 0)
a = ClassA()
print gc.get_count() # (591, 8, 0)
del a
print gc.get_count() # (590, 8, 0)
3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。
gc模快有一个自动垃圾回收的阀值,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10) 每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器
例如,假设阀值是(700,10,10):
当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0)
当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1)
当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)
注意点
gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法
import gc
class ClassA():
pass
# def __del__(self):
# print('object born,id:%s'%str(hex(id(self))))
gc.set_debug(gc.DEBUG_LEAK)
a = ClassA()
b = ClassA()
a.next = b
b.prev = a
print "--1--"
print gc.collect()
print "--2--"
del a
print "--3--"
del b
print "--3-1--"
print gc.collect()
print "--4--"
运行结果:
--1--
0
--2--
--3--
--3-1--
gc: collectable
gc: collectable
gc: collectable
gc: collectable
4
--4--
如果把del打开,运行结果为:
--1--
0
--2--
--3--
--3-1--
gc: uncollectable
gc: uncollectable
gc: uncollectable
gc: uncollectable
4
--4--
[if !supportLists]1.12 [endif]内建属性
"teachclass.py"
class Person(object):
pass
python3.5中类的内建属性和方法
经典类(旧式类),早期如果没有要继承的父类,继承里空着不写的类
#py2中无继承父类,称之经典类,py3中已默认继承object
class Person:
pass
子类没有实现__init__方法时,默认自动调用父类的。如定义__init__方法时,需自己手动调用父类的__init__方法
__getattribute__例子:
class Test(object):
def __init__(self,subject1):
self.subject1 = subject1
self.subject2 = 'cpp'
#属性访问时拦截器,打log
def __getattribute__(self,obj):
if obj == 'subject1':
print('log subject1')
return 'redirect python'
else: #测试时注释掉这2行,将找不到subject2
return object.__getattribute__(self,obj)
def show(self):
print('this is Test')
s = Test("python")
print(s.subject1)
print(s.subject2)
运行结果:
log subject1
redirect python
cpp
__getattribute__的坑
class Person(object):
def __getattribute__(self,obj):
print("---test---")
if obj.startswith("a"):
return "hahha"
else:
return self.test
def test(self):
print("heihei")
t = Person()
t.a #返回hahha
t.b #会让程序死掉
#原因是:当t.b执行时,会调用Person类中定义的__getattribute__方法,但是在这个方法的执行过程中
#if条件不满足,所以 程序执行else里面的代码,即return self.test 问题就在这,因为return 需要把
#self.test的值返回,那么首先要获取self.test的值,因为self此时就是t这个对象,所以self.test就是
#t.test此时要获取t这个对象的test属性,那么就会跳转到__getattribute__方法去执行,即此时产
#生了递归调用,由于这个递归过程中 没有判断什么时候推出,所以这个程序会永无休止的运行下去,又因为
#每次调用函数,就需要保存一些数据,那么随着调用的次数越来越多,最终内存吃光,所以程序 崩溃
#
#注意:以后不要在__getattribute__方法中调用self.xxxx
[if !supportLists]1.13 [endif]内建函数
Build-in Function,启动python解释器,输入dir(__builtins__),可以看到很多python解释器启动后默认加载的属性和函数,这些函数称之为内建函数, 这些函数因为在编程时使用较多,cpython解释器用c语言实现了这些函数,启动解释器 时默认加载。
这些函数数量众多,不宜记忆,开发时不是都用到的,待用到时再help(function),查看如何使用,或结合百度查询即可,在这里介绍些常用的内建函数。
[if !supportLists]1.13.1 [endif]range
range(stop) -> list of integers
range(start, stop[, step]) -> list of integers
[if !supportLists]· [endif]start:计数从start开始。默认是从0开始。例如range(5)等价于range(0, 5);
[if !supportLists]· [endif]stop:到stop结束,但不包括stop.例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
[if !supportLists]· [endif]step:每次跳跃的间距,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
python2中range返回列表,python3中range返回一个迭代值。如果想得到列表,可通过list函数
a = range(5)
list(a)
创建列表的另外一种方法
In [21]: testList = [x+2 for x in range(5)]
In [22]: testList
Out[22]: [2, 3, 4, 5, 6]
[if !supportLists]1.13.2 [endif]map函数
map函数会根据提供的函数对指定序列做映射
map(...)
map(function, sequence[, sequence, ...]) -> list
[if !supportLists]· [endif]function:是一个函数
[if !supportLists]· [endif]sequence:是一个或多个序列,取决于function需要几个参数
[if !supportLists]· [endif]返回值是一个map
参数序列中的每一个元素分别调用function函数,返回包含每次function函数返回值的list。
#函数需要一个参数
map(lambda x: x*x, [1, 2, 3])
#结果为:[1, 4, 9]
#函数需要两个参数
map(lambda x, y: x+y, [1, 2, 3], [4, 5, 6])
#结果为:[5, 7, 9]
def f1( x, y ):
return (x,y)
l1 = [ 0, 1, 2, 3, 4, 5, 6 ]
l2 = [ 'Sun', 'M', 'T', 'W', 'T', 'F', 'S' ]
l3 = map( f1, l1, l2 )
print(list(l3))
#结果为:[(0, 'Sun'), (1, 'M'), (2, 'T'), (3, 'W'), (4, 'T'), (5, 'F'), (6, 'S')]
[if !supportLists]1.13.3 [endif]filter函数
filter函数会对指定序列执行过滤操作
filter(...)
filter(function or None, sequence) -> list, tuple, or string
Return those items of sequence for which function(item) is true. If
function is None, return the items that are true. If sequence is a tuple
or string, return the same type, else return a list.
[if !supportLists]· [endif]function:接受一个参数,返回布尔值True或False
[if !supportLists]· [endif]sequence:序列可以是str,tuple,list
filter函数会对序列参数sequence中的每个元素调用function函数,最后返回的结果包含调用结果为True的元素。
返回值的类型和参数sequence的类型相同
filter(lambda x: x%2, [1, 2, 3, 4])
[1, 3]
filter(None, "she")
'she'
[if !supportLists]1.13.4 [endif]reduce函数
reduce函数,reduce函数会对参数序列中元素进行累积
reduce(...)
reduce(function, sequence[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence,
from left to right, so as to reduce the sequence to a single value.
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the
sequence is empty.
[if !supportLists]· [endif]function:该函数有两个参数
[if !supportLists]· [endif]sequence:序列可以是str,tuple,list
[if !supportLists]· [endif]initial:固定初始值
reduce依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。 第一次调用function时,如果提供initial参数,会以sequence中的第一个元素和initial 作为参数调用function,否则会以序列sequence中的前两个元素做参数调用function。 注意function函数不能为None。
reduce(lambda x, y: x+y, [1,2,3,4])
10
reduce(lambda x, y: x+y, [1,2,3,4], 5)
15
reduce(lambda x, y: x+y, ['aa', 'bb', 'cc'], 'dd')
'ddaabbcc'
在Python3里,reduce函数已经被从全局名字空间里移除了, 它现在被放置在fucntools模块里用的话要先引入: from functools import reduce
[if !supportLists]1.13.5 [endif]sorted函数
sorted(...)
sorted(iterable, key=None, reverse=False) --> new sorted list
[if !supportLists]1.14 [endif]functools
functools是python2.5被引人的,一些工具函数放在此包里。
python2.7中
python3.5中
import functools
dir(functools)
运行结果:
['MappingProxyType',
'RLock',
'WRAPPER_ASSIGNMENTS',
'WRAPPER_UPDATES',
'WeakKeyDictionary',
'_CacheInfo',
'_HashedSeq',
'__all__',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'_c3_merge',
'_c3_mro',
'_compose_mro',
'_convert',
'_find_impl',
'_ge_from_gt',
'_ge_from_le',
'_ge_from_lt',
'_gt_from_ge',
'_gt_from_le',
'_gt_from_lt',
'_le_from_ge',
'_le_from_gt',
'_le_from_lt',
'_lru_cache_wrapper',
'_lt_from_ge',
'_lt_from_gt',
'_lt_from_le',
'_make_key',
'cmp_to_key',
'get_cache_token',
'lru_cache',
'namedtuple',
'partial',
'partialmethod',
'reduce',
'singledispatch',
'total_ordering',
'update_wrapper',
'wraps']
python3中增加了更多工具函数,做业务开发时大多情况下用不到,此处介绍使用频率较高的2个函数。
[if !supportLists]1.14.1 [endif]partial函数(偏函数)
把一个函数的某些参数设置默认值,返回一个新的函数,调用这个新函数会更简单。
import functools
def showarg(*args, **kw):
print(args)
print(kw)
p1=functools.partial(showarg, 1,2,3)
p1()
p1(4,5,6)
p1(a='python', b='test')
p2=functools.partial(showarg, a=3,b='linux')
p2()
p2(1,2)
p2(a='python', b='test')
[if !supportLists]1.14.2 [endif]wraps函数
使用装饰器时,有一些细节需要被注意。例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。
添加后由于函数名和函数的doc发生了改变,对测试结果有一些影响,例如:
def note(func):
"note function"
def wrapper():
"wrapper function"
print('note something')
return func()
return wrapper
@note
def test():
"test function"
print('I am test')
test()
print(test.__doc__)
运行结果
note something
I am test
wrapper function
所以,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用。例如:
import functools
def note(func):
"note function"
@functools.wraps(func)
def wrapper():
"wrapper function"
print('note something')
return func()
return wrapper
@note
def test():
"test function"
print('I am test')
test()
print(test.__doc__)
运行结果
note something
I am test
test function
[if !supportLists]1.15 [endif]模块进阶
Python有一套很有用的标准库(standard library)。标准库会随着Python解释器,一起安装在你的电脑中的。 它是Python的一个组成部分。这些标准库是Python为你准备好的利器,可以让编程事半功倍。
[if !supportLists]1.15.1 [endif]常用标准库
[if !supportLists]1.15.1.1 [endif]time
1、help(time)之后可以知道time有2种时间表示形式:
1、时间戳表示法,即以整型或浮点型表示的是一个以秒为单位的时间间隔。这个时间的基础值是从1970年的1月1号零点开始算起。2、元组格式表示法,即一种Python的数据结构表示。这个元组有9个整型内容。分别表示不同的时间含义。
2、名词解释:
UTC(Coordinated Universal Time,世界协调时)亦即格林威治天文时间,世界标准时间。在中国为UTC+8。
DST(Daylight Saving Time)即夏令时。是一种为节约能源而人为规定地方时间的制度,一般在天亮早的夏季人为将时间提前一小时。
3、包含的变量:
timezone --当地时间与标准UTC时间的误差,以秒计altzone --当地夏令时时间与标准UTC时间的误差,以秒计daylight --当地时间是否反映夏令时,默认为0zname --关于(标准时区名称, 夏令时时区名称)的元组
4、包含的函数:
time() --返回当前时间戳,浮点数形式。不接受参数clock() --返回当前程序的cpu执行时间。unix系统始终返回全部运行时间;而windows从第二次开始都是以第一次调用此函数时的时间戳作为基准,而不是程序开始时间为基准。不接受参数。sleep() --延迟一个时间段,接受整型、浮点型。gmtime() --将时间戳转换为UTC时间元组格式。接受一个浮点型时间戳参数,其默认值为当前时间戳。
localtime() --将时间戳转换为本地时间元组格式。接受一个浮点型时间戳参数,其默认值为当前时间戳。asctime() --将时间元组格式转换为字符串形式。接受一个时间元组,其默认值为localtime()返回值ctime() --将时间戳转换为字符串。接受一个时间戳,其默认值为当前时间戳。等价于asctime(localtime(seconds))mktime() --将本地时间元组转换为时间戳。接受一个时间元组,必选。strftime() --将时间元组以指定的格式转换为字符串形式。接受字符串格式化串、时间元组。时间元组为可选,默认为localtime()strptime() --将指定格式的时间字符串解析为时间元组,strftime()的逆向过程。接受字符串,时间格式2个参数,都是必选。tzset() --改变本地时区。
5、时间字符串支持的格式符号:
格式 含义 备注
%a 本地(locale)简化星期名称
%A 本地完整星期名称
%b 本地简化月份名称
%B 本地完整月份名称
%c 本地相应的日期和时间表示
%d 一个月中的第几天(01 - 31)
%H 一天中的第几个小时(24小时制,00 - 23)
%I 第几个小时(12小时制,01 - 12)
%j 一年中的第几天(001 - 366)
%m 月份(01 - 12)
%M 分钟数(00 - 59)
%p 本地am或者pm的相应符
%S 秒(01 - 61)
%U 一年中的星期数。(00 - 53星期天是一个星期的开始。)第一个星期天之前的所有天数都放在第0周。
%w 一个星期中的第几天(0 - 6,0是星期天)
%W 和%U基本相同,不同的是%W以星期一为一个星期的开始。
%x 本地相应日期
%X 本地相应时间
%y 去掉世纪的年份(00 - 99)
%Y 完整的年份
%Z 时区的名字(如果不存在为空字符)
%% ‘%’字符
[if !supportLists]1.15.1.2 [endif]hashlib
import hashlib
m = hashlib.md5() #创建hash对象,md5:(message-Digest Algorithm 5)消息摘要算法,得出一个128位的密文
print m #
m.update('test'.encode(“utf-8”)) #更新哈希对象以字符串参数
print m.hexdigest() #返回十六进制数字字符串
应用实例
用于注册、登录....
import hashlib
import datetime
KEY_VALUE = 'test'
now = datetime.datetime.now()
m = hashlib.md5()
str = '%s%s' % (KEY_VALUE,now.strftime("%Y%m%d"))
m.update(str.encode('utf-8'))
value = m.hexdigest()
print(value)
运行结果:
8ad2d682e3529dac50e586fee8dc05c0
更多标准库
http://python.usyiyi.cn/translate/python_352/library/index.html
[if !supportLists]1.15.2 [endif]常用扩展库
[if !supportLists]1.15.2.1 [endif]SimpleHTTPServer
就可以运行起来静态服务。平时用它预览和下载文件太方便了。
在终端中输入命令:
python2中
python -m SimpleHTTPServer PORT
python3中
python -m http.server PORT
[if !supportLists]1.15.2.2 [endif]matplotlib
[if !supportLists]1.16 [endif] 编码风格
错误认知
[if !supportLists]· [endif]这很浪费时间
[if !supportLists]· [endif]我是个艺术家
[if !supportLists]· [endif]所有人都能穿的鞋不会合任何人的脚
[if !supportLists]· [endif]我善长制定编码规范
正确认知
[if !supportLists]· [endif]促进团队合作
[if !supportLists]· [endif]减少bug处理
[if !supportLists]· [endif]提高可读性,降低维护成本
[if !supportLists]· [endif]有助于代码审查
[if !supportLists]· [endif]养成习惯,有助于程序员自身的成长
pep8编码规范
Python Enhancement Proposals:python改进方案
https://www.python.org/dev/peps/
pep8官网规范地址
https://www.python.org/dev/peps/pep-0008/
Guido的关键点之一是:代码更多是用来读而不是写。编码规范旨在改善Python代码的可读性。
风格指南强调一致性。项目、模块或函数保持一致都很重要。
[if !supportLists]1.16.1 [endif]每级缩进用4个空格。
括号中使用垂直隐式缩进或使用悬挂缩进。后者应该注意第一行要没有参数,后续行要有缩进。
[if !supportLists]· [endif]Yes
#对准左括号
foo = long_function_name(var_one, var_two,
var_three, var_four)
#不对准左括号,但加多一层缩进,以和后面内容区别。
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
#悬挂缩进必须加多一层缩进.
foo = long_function_name(
var_one, var_two,
var_three, var_four)
[if !supportLists]· [endif]No
#不使用垂直对齐时,第一行不能有参数。
foo = long_function_name(var_one, var_two,
var_three, var_four)
#参数的缩进和后续内容缩进不能区别。
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
4个空格的规则是对续行可选的。
#悬挂缩进不一定是4个空格
foo = long_function_name(
var_one, var_two,
var_three, var_four)
if语句跨行时,两个字符关键字(比如if)加上一个空格,再加上左括号构成了很好的缩进。后续行暂时没有规定,至少有如下三种格式,建议使用第3种。
#没有额外缩进,不是很好看,个人不推荐.
if (this_is_one_thing and
that_is_another_thing):
do_something()
#添加注释
if (this_is_one_thing and
that_is_another_thing):
# Since both conditions are true, we can frobnicate.
do_something()
#额外添加缩进,推荐。
# Add some extra indentation on the conditional continuation line.
if (this_is_one_thing
and that_is_another_thing):
do_something()
[if !supportLists]1.16.2 [endif]右边括号也可以另起一行。有两种格式,建议第2种。
#右括号不回退,个人不推荐
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
#右括号回退
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
[if !supportLists]1.16.3 [endif]空格或Tab?
[if !supportLists]· [endif]空格是首选的缩进方法。
[if !supportLists]· [endif]Tab仅仅在已经使用tab缩进的代码中为了保持一致性而使用。
[if !supportLists]· [endif]Python 3中不允许混合使用Tab和空格缩进。
[if !supportLists]· [endif]Python 2的包含空格与Tab和空格缩进的应该全部转为空格缩进。
[if !supportLists]1.16.4 [endif]最大行宽
[if !supportLists]· [endif]限制所有行的最大行宽为79字符。
[if !supportLists]· [endif]文本长块,比如文档字符串或注释,行长度应限制为72个字符。
[if !supportLists]1.16.5 [endif]空行
[if !supportLists]· [endif]两行空行分割顶层函数和类的定义。
[if !supportLists]· [endif]类的方法定义用单个空行分割。
[if !supportLists]· [endif]额外的空行可以必要的时候用于分割不同的函数组,但是要尽量节约使用。
[if !supportLists]· [endif]额外的空行可以必要的时候在函数中用于分割不同的逻辑块,但是要尽量节约使用。
[if !supportLists]1.16.6 [endif]源文件编码
[if !supportLists]· [endif]在核心Python发布的代码应该总是使用UTF-8(ASCII在Python 2)。
[if !supportLists]· [endif]Python 3(默认UTF-8)不应有编码声明。
[if !supportLists]1.16.7 [endif]导入在单独行
[if !supportLists]· [endif]Yes:
import os
import sys
from subprocess import Popen, PIPE
[if !supportLists]· [endif]No:
import sys, os
[if !supportLists]· [endif]导入始终在文件的顶部,在模块注释和文档字符串之后,在模块全局变量和常量之前。
[if !supportLists]· [endif]导入顺序如下:标准库进口,相关的第三方库,本地库。各组的导入之间要有空行。
[if !supportLists]1.16.8 [endif]禁止使用通配符导入。
通配符导入(from import *)应该避免,因为它不清楚命名空间有哪些名称存,混淆读者和许多自动化的工具。
[if !supportLists]1.16.9 [endif]字符串引用
[if !supportLists]· [endif]Python中单引号字符串和双引号字符串都是相同的。注意尽量避免在字符串中的反斜杠以提高可读性。
[if !supportLists]· [endif]根据PEP 257,三个引号都使用双引号。
[if !supportLists]1.16.10 [endif]括号里边避免空格
#括号里边避免空格
# Yes
spam(ham[1], {eggs: 2})
# No
spam( ham[ 1 ], { eggs: 2 } )
[if !supportLists]1.16.11 [endif]逗号,冒号,分号之前避免空格
#逗号,冒号,分号之前避免空格
# Yes
if x == 4: print x, y; x, y = y, x
# No
if x == 4 : print x , y ; x , y = y , x
[if !supportLists]1.16.12 [endif]索引操作中的冒号
当作操作符处理前后要有同样的空格(一个空格或者没有空格,个人建议是没有。
# Yes
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]
# No
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
[if !supportLists]1.16.13 [endif]函数调用的左括号之前不能有空格
# Yes
spam(1)
dct['key'] = lst[index]
# No
spam (1)
dct ['key'] = lst [index]
[if !supportLists]1.16.14 [endif]赋值等操作符前后
赋值等操作符前后不能因为对齐而添加多个空格
# Yes
x = 1
y = 2
long_variable = 3
# No
x = 1
y = 2
long_variable = 3
[if !supportLists]1.16.15 [endif]二元运算符两边放置一个空格
涉及=、符合操作符 ( += , -=等)、比较( == , < , > , != , <> , <= , >= , in , not in , is , is not )、布尔( and , or , not )。
优先级高的运算符或操作符的前后不建议有空格。
# Yes
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
# No
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
[if !supportLists]1.16.16 [endif]关键字参数和默认值参数的前后不要加空格
# Yes
def complex(real, imag=0.0):
return magic(r=real, i=imag)
# No
def complex(real, imag = 0.0):
return magic(r = real, i = imag)
[if !supportLists]1.16.17 [endif]通常不推荐复合语句
Compound statements:多条语句写在同一行
# Yes
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()
# No
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
尽管有时可以在if/for/while的同一行跟一小段代码,但绝不要跟多个子句,并尽量避免换行。
# No
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
更不是:
# No
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()
try: something()
finally: cleanup()
do_one(); do_two(); do_three(long, argument,
list, like, this)
if foo == 'blah': one(); two(); three()
[if !supportLists]1.16.18 [endif]避免采用的名字
决不要用字符'l'(小写字母el),'O'(大写字母oh),或 'I'(大写字母eye) 作为单个字符的变量名。一些字体中,这些字符不能与数字1和0区别。用'L' 代替'l'时。
[if !supportLists]1.16.19 [endif]包和模块名
模块名要简短,全部用小写字母,可使用下划线以提高可读性。包名和模块名类似,但不推荐使用下划线。
[if !supportLists]1.17 [endif]代码调试
[if !supportLists]1.17.0.1 [endif]pycharm
步骤:
[if !supportLists]1、 [endif]设置断点
[if !supportLists]2、 [endif]shift+f9 开始调试
[if !supportLists]3、 [endif]光标就在断点处停了。这一行没有运行的
[if !supportLists]4、 [endif]下一行:f8
[if !supportLists]5、 [endif]进入方法:f7
[if !supportLists]6、 [endif]跳到下一个断点:alt+f9
[if !supportLists]7、 [endif]进入方法,跳出这一步,shift+f8