range
: range([start,]stop[,step])
根据start
与stop
指定的范围以及step
设定的步长生成一个序列。xrange
: 用法与range
完全相同,所不同的是生成的不是一个数组,而是一个生成器生成器
要生成很大数字序列的时候,使用
xrange
比range
性能优很多,因为不需要一上来就开辟一块很大的内存空间。
都是python
字典的内建函数,分别会返回python列表
和迭代器
items
: 可以将字典中的所有项,以列表方式返回。因为字典是无序的,所有用items方法返回字典的所有项,也是没有顺序的。items()
方法将字典中的每项分别作为元组,添加到一个列表中,形成一个新的列表容器。iteritems
: 与items
方法相比作用大致相同,只是返回值不是列表而是一个迭代器.
dic.iteritems()
方法在需要迭代结果时使用最合适,而且它的工作效率非常高。enumerate()
函数用于将一个可遍历的数据对象(如列表,元祖或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for
循环中。
enumerate(sequence, [start=0])
sequenece
: 一个序列,迭代器或其他支持迭代的对象。start
: 下标起始位置返回值
: 返回enumerate(枚举)
对象
>>>> seasons = ['Spring','Summer','Fall','Winter']
>>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>>> list(enumerate(seasons, 1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter') ]
Python
内置模块itertools
提供非常有用的用于操作迭代对象的函数。其中combinations()
可以生成一个list
的全排列。题目举例https://leetcode.com/problems/largest-triangle-area/description/
枚举所有三角形计算出面积最大的面积,多边形面积计算公式可以参考 https://en.wikipedia.org/wiki/Shoelace_formula
# -*- coding: UTF-8 -*-
#!usr/bin/env python3
import itertools
class Solution:
@staticmethod
def area(p1, p2, p3):
return .5 * abs(p1[0]*p2[1] + p2[0]*p3[1] + p3[0]*p1[1] -
p2[0]*p1[1] - p3[0]*p2[1] - p1[0]*p3[1])
def largestTriangleArea(self, points):
"""
:type points: List[List[int]]
:rtype: float
"""
return max(Solution.area(*triangle)
for triangle in itertools.combinations(points, 3))
if __name__ == "__main__":
tester = Solution()
print(tester.largestTriangleArea([[0,0],[0,1],[1,0],[0,2],[2,0]]))
itertools
包中 itertools.chain.from_iterable
轻松跨素的辗平一个列表:import itertools
a_list = [[1, 2], [3, 4], [5, 6]]
print(list(itertools.chain.from_iterable(a_list)))
#Output: [1, 2, 3, 4, 5, 6]
print(list(itertools.chain(*a_list)))
#Output: [1, 2, 3, 4, 5, 6]
以下原文: https://eastlakeside.gitbooks.io/interpy-zh/content/Generators/Generators.html
*args
*args
和**kwargs
主要用于函数定义。可以将不定数量的参数传递给一个函数。
def test_var_args(f_arg, *argv):
print("first normal arg:", f_arg)
for arg in argv:
print("another arg through *argv:", arg)
test_var_args('yasoob', 'python', 'eggs', 'test')
## Output:
('first normal arg:', 'yasoob')
('another arg through *argv:', 'python')
('another arg through *argv:', 'eggs')
('another arg through *argv:', 'test')
**kwargs
**kwargs
允许将不定长度的键值对,作为参数传递给一个函数。如果想要一个函数里处理带名字的参数, 应该使用**kwargs
.
def greet_me(**kwargs):
for key,value in kwargs.items():
print("{0} == {1}".format(key, value))
greet_me(name="yasoob")
## Output:
name == yasoob
标准参数与 *args, **kwargs 在使用时的顺序
: some_func(fargs, *args, **kwargs)
根据维基百科,迭代器是一个让程序员可以遍历一个容器(特别是列表)的对象。然而,一个迭代器在遍历并读取一个容器的数据元素时,并不会执行一个迭代。迭代器可以概括为:
Iterable
):Python
中任意对象,只要它定义了可返回一个迭代器__iter__
方法,或者定义可以支持下标索引的__getitem__
方法, 那么它就是一个可迭代对象。Iterator
): 任意对象,只要定义next
(Pthon2)或者__next__
方法,它就是一个迭代器。Iteration
):从某个地方(比如一个列表)取出一个元素的过程。当我们使用循环来遍历某个东西时,这个过程本身就叫迭代。
生成器也是一种迭代器,但是只能对其迭代一次。这是因为它并没有把所有的值存在内存中,而是在运行时生成值。通过遍历来使用它们,要么用
for
循环,要么将它们传递给任意可以进行迭代的函数和结构。大多数时候生成器是以函数来实现的。然而,它并不返回值,而是yield
一个值.
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
for x in fibon(1000000):
print(x)
这种方式, 可以不用担心它会使用大量资源。
Python
中str
对象不是一个迭代器,但是是可迭代对象, 内置函数iter
会根据一个可迭代对象返回一个迭代器对象。my_string = "Yasoob" my_iter = iter(my_string) print(next(my_iter)) # Output: 'Y'
Map
Map
会将一个函数映射到一个输入列表的所有元素上。
map(function_to_apply, list_of_inputs)
例子1:
items = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, items))
例子2:
def multiply(x):
return (x*x)
def add(x):
return (x+x)
funcs = [multiply, add]
for i in range(5):
value = map(lambda x: x(i), funcs)
print(list(value))
# 加`list`转换是为了python2/3兼容
# python2中map直接返回列表, python3中返回迭代器
Filter
filter
过滤列表中的元素,并且返回一个由所有符合要求(函数映射到该元素时返回值为True
)的元素所构成的列表。
filter(function, iterable)
function
: 判断函数iterable
: 可迭代对象
number_list = range(-5, 5)
less_than_zero = filter(lambda x: (x < 0), number_list)
print(list(less_than_zero))
Reduce
当需要对一个列表进行一些计算并返回结果时, reduce
是个非常有用的函数。
reduce(function, iterable[, initializer])
function
: 函数有两个参数iterable
: 可迭代对象initializer
: 可选,开始参数
from functools import reduce
product = reduce((lambda x, y: x*y), [1, 2, 3, 4])
# Output: 24
set
(集合)是一个非常有用的数据结构。它与列表(list
)的行为类似,区别在于set
不能包含重复的值。假设要检查列表中是否包含重复元素,可以有两种方法:
方式一:
some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n']
duplicates = []
for value in some_list:
if some_list.count(value) > 1:
if value not in duplicates:
duplicates.append(value)
print(duplicates)
# Output: ['b', 'n']
方式二:
some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n']
duplicates = set([x for x in some_list if some_list.count(x) > 1])
print(duplicates)
#Output: set(['b', 'n'])
valid = set(['yellow', 'red', 'blue', 'green', 'black'])
input_set = set(['red', 'brown'])
print(input_set.intersection(valid))
#Output: set(['red'])
# -*- coding: UTF-8 -*-
#!usr/bin/env python
valid = set(['yellow', 'red', 'blue', 'green', 'black'])
input_set = set(['red', 'brown'])
print(input_set.difference(valid))
#Output: set(['brown'])
# 也可以用`{}`符号来创建集合
a_set = {'red', 'blue', 'green'}
print(type(a_set))
## Output: <type 'set'>
三元运算符在Python
里通常被称为条件表达式,这些表达式基于True False
的条件判断,在Python 2.4
以上才有三元操作。
condition_is_true if condition else condition_is_false
is_fat = True
state = "fat" is_fat else "not fat"
装饰器(Decorators)是Python
的一个重要部分。简单的说:他们是修改其他函数的功能函数,他们有助于让我们的代码更简短。
Python
中一切皆对象def hi(name="yasoob"):
return "hi " + name
print(hi())
#Output: hi yasoob
greet = hi
print(greet())
#Output: hi yasoob
del hi
print(hi())
#Output: NameError
print(greet())
#Output: hi yasoob
def hi(name="yasoob"):
print("now you are inside the hi() function")
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
print(greet())
print(welcome())
print("now you are back in the hi() function")
hi()
#Output: now you are inside the hi() function
# now you are in the greet() function
# now you are in the welcome() function
# now you are back in the hi() function
def hi(name="yasoob"):
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
if name == "yasoob":
return greet
else:
return welcome
a = hi()
print(a)
#Output: <function greet at 0x10a64e050>
print(a())
#Output: now you are in the greet() function
def hi():
return "hi yasoob!"
def doSomethingBeforeHi(func):
print("I am doing some boring work before executing hi()")
print(func())
doSomethingBeforeHi(hi)
#Output: I am doing some boring work before executing hi()
# hi yasoob!
def a_new_decorator(a_func):
def wrapTheFUnction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFUnction
def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
#Output: I am the function which needs some decoration to remove my foul smell
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
# now a_function_requiring_decoration is wrapped by wrapTheFUnction()
a_function_requiring_decoration()
#Output: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
这个就是
Python
中装饰器做的事,它们封装一个函数,并且用这样或那样的方式来修改它的行为。@
符号只是用一个简短的方式来生成一个被装饰的函数。可以修改成@
来运行之前的代码:
def a_new_decorator(a_func):
def wrapTheFUnction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFUnction
a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
#Output: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
但是我们运行如下代码会存在一个问题:
print(a_function_requiring_decoration.__name__)
#Output: wrapTheFunction
这不是我们想要的!
Output
输出应该是a_function_requiring_decoration
。 这里的函数被wrapTheFunction
替代了。 它重写了我们函数的名字和注释文档(docstring
)。幸运的是Python
提供给我们一个简单的函数来解决这个问题, 那就是functools.wraps
:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to remove my foul smell")
print(a_function_requiring_decoration.__name__, a_function_requiring_decoration.__doc__)
#Output: ('a_function_requiring_decoration', 'Hey you! Decorate me!')
装饰器有一些使用场景:
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
decorator_name
def func():
return "Function is running"
can_run = True
print(func())
#Output: Function is running
can_run = False
print(func())
#Output: Function will not run
@wraps
接受一个函数来进行装饰,并加入复制函数名称,注释文档,参数列表等功能。这可以让我们在装饰器里访问在装饰之前函数的属性。
web
应用的端点(endpoint)。它们被大量使用于Flask
和Django Web
框架中。from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if (not auth) or (not check_auth(auth.username, auth.password)):
authenticate()
return f(*args, **kwargs)
return decorated
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " war called")
return func(*args, **kwargs)
return with_logging
logit
def addition_func(x):
"""Do some math."""
return x + x
print(addition_func(4))
#Output: addition_func war called
# 8
@wraps
也是一个装饰器,接收一个参数,就像任何普通函数能做的那样。 为什么我们不也那样做? 这是因为,当使用 @my_decorator
语法时,是在应用一个以单个函数作为参数的一个包裹函数。Python
中每个东西都是一个对象,我们可以编写一个能返回一个包裹函数的函数。
from functools import wraps
def logit(logfile='out.log'):
def loggine_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
with open(logfile, 'a') as opened_file:
opened_file.write(log_string + ' \n')
return func(*args, **kwargs)
return wrapped_function
return loggine_decorator
@logit()
def myfunc1():
pass
myfunc1()
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
现在我们有了能用于正式环境的logit
装饰器,但我们应用的某些部分还是比较脆弱的,异常也许是需要更紧急关注的事情。比如只想打日志到一个文件,而有时想把问题发送一个 email
, 同时也保留日志,留个记录。这是一个使用继承的场景,幸运的是,类也可以用来构建装饰器。我们以一个类而不是一个函数来重新构建logit
。
# -*- coding: UTF-8 -*-
#!usr/bin/env python
import types
from functools import wraps
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile并写入
with open(self.logfile, 'a') as opened_file:
# 现在将日志打到指定的文件
opened_file.write(log_string + '\n')
# 现在,发送一个通知
self.notify()
return func(*args, **kwargs)
return wrapped_function
def notify(self):
# logit只打日志,不做别的
print(self.logfile)
pass
@logit(logfile='zhang.log')
def myfunc1():
pass
myfunc1()
#Output: myfunc1 was called
# zhang.log
class email_logit(logit):
'''
一个logit的实现版本,可以在函数调用时发送email给管理员
'''
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# 发送一封email到self.email
print(self.email)
pass
@email_logit(email='garretthh07@gmail.com')
def myfunc2():
pass
myfunc2()
#Output: myfunc2 was called
# garretthh07@gmail.com
Python
中可变(mutable)意味”可以被改动”, 不可变(immutable)就是常量(constant)。
# -*- coding: UTF-8 -*-
#!usr/bin/env python
foo = ['hi']
print(foo)
#Output: ['hi']
bar = foo
bar += ['bye']
print(foo)
#Output: ['hi', 'bye']
这不是一个bug
。这是由于对象可变性(mutability),每当将一个变量赋值为另一个可变类型的变量时,对这个数据的任意改动会同时反应到这两个变量上去。新变量只不过是老变量的一个别名而已。
# -*- coding: UTF-8 -*-
#!usr/bin/env python
def add_to(num, target=[]):
target.append(num)
return target
print(add_to(1))
#Output: [1]
print(add_to(2))
#Output: [1, 2]
print(add_to(3))
#Output: [1, 2, 3]
这是列表的可变性在作怪。在Python
中当函数被定义时,默认参数只会运算一次,而不是每次被调用时都会重新运算。永远都不要定义可变类型的默认参数。
# -*- coding: UTF-8 -*-
#!usr/bin/env python
def add_to(num, target=None):
if target is None:
target = []
target.append(num)
return target
print(add_to(1))
#Output: [1]
print(add_to(2))
#Output: [2]
print(add_to(3))
#Output: [3]
__slots__
魔法在Python
中,每个类都有实例属性。默认情况下Python
用一个字典来保存一个对象的实例属性。它允许我们在运行时去设置任意的新属性。然而,对于有着已知属性的小类来说,它可能是个瓶颈。这个字典浪费类很多内存。Python
不能在对象创建时直接分配一个固定量的内存来保存所有的属性。因此如果你创建许多对象,它会消耗掉很多内存。不过还是有一个方法来规避这个问题。这个方法需要使用 __slots__
来告诉Python
不要使用字典,而且只给一个固定集合的属性分配空间。
__slots__
class MyClass(object):
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
__slots__
class MyClass(object):
__slots__ = ['name', 'identifier']
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
第二段代码会为你的内存减轻负担。PyPy
已经默认做了所有这些优化。https://github.com/ianozsvald/ipython_memory_usage可以用来查看在有与没有__slots
时精确的内存占用。
virtualenv
是一个工具,能帮我们创建一个独立的Python
环境。如果一个应用程序,依赖版本为2
的第三方模块,另一个程序依赖版本为3
, 怎么使用和开发这些应用程序?
如果把一切安装到/usr/lib/python2.7/site-packages
(或其他平台的标准位置), 那很容易出现某个模块被升级而你却不知道的情况。 另一种情况,假如你已开发完成一个程序,但你不想更新它所依赖的第三方模块版本;但开始的另一程序,需要这些第三方模块版本。
使用virtualenv
针对每个程序创建独立的Python
环境,而不是在全局安装所依赖的模块。安装命令:
# install
sudo easy_install pip
sudo pip install virtualenv
sudo pip install nose
sudo pip install tornado
# run
virtualenv myproject
source myproject/bin/activate
执行第一个命令在myproject
文件夹创建一个隔离的virtualenv
环境,第二个命令激活这个隔离的环境(virtualenv)。在创建virtualenv
时,必须作出决定:这个virtualenv
是使用系统全局的模块,还是只使用这个virtualenv
内的模块。 默认情况下,virtualenv
不会使用系统全局模块。如果想让virtualenv
使用系统全局模块,可以:
virtualenv --system-site-packages mycoolproject
使用以下命令可以退出这个virtualenv
, 运行后会恢复使用系统全局的Python
模块:
deactivate
可以使用 https://github.com/cxreg/smartcd来帮助管理环境,当切换目录时,可以帮助你激活和退出你的virtualenv
。
Python
附带一个模块,包含许多容器数据类型,叫作collections
。我们讨论它的作用和用法:
dict
类型不同,不需要检查key
是否存在:from collections import defaultdict
colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)
favourite_colours = defaultdict(list)
for name,colour in colours:
favourite_colours [name].append(colour)
print(favourite_colours)
#Output: defaultdict(<type 'list'>,
# {'Arham': ['Green'], 'Yasoob': ['Yellow', 'Red'], 'Ahmed': ['Silver'], 'Ali': ['Blue', 'Black']})
当一个字典中对一个键进行潜逃赋值时,如果这个键不存在,会触发keyError
异常。defaultdict
允许我们用一个聪明的方式绕过这个问题。
'''
some_dict = {}
some_dict['colours']['favourite'] = "yellow"
'''
#Output: KeyError: 'colours'
import collections, json
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"
print(json.dumps(some_dict))
#Output: {"colours": {"favourite": "yellow"}}
Counter
是一个计数器,可以帮助我们针对某项数据进行计数。比如可以用来计算每个人喜欢多少种颜色:# -*- coding: UTF-8 -*-
#!usr/bin/env python
from collections import Counter
colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)
favs = Counter(name for name, coulout in colours)
print(favs)
# Output: Counter({'Yasoob': 2, 'Ali': 2, 'Arham': 1, 'Ahmed': 1})
with open('filename', 'a') as f:
f.write('test01' + '\n')
# 可以用来统计一个文件相同的行
with open('filename', 'rb') as f:
line_count = Counter(f)
print(line_count)
#Output: Counter({'test01\n': 3, 'test\n': 2})
deque
提供一个双端队列,可以从头/尾添加或删除元素, 提供来类似list
类似的方法,也可以从两端pop
数据。我们也可以限制这个列表的大小,当超出设定的限制时,数据会从队列另一端被pop
出去。# -*- coding: UTF-8 -*-
#!usr/bin/env python
from collections import deque
d = deque(maxlen = 30)
d.append('1')
d.append('2')
print(len(d), d[0], d[-1])
#Output: (2, '1', '2')
d = deque(range(5))
print(len(d))
#Output: 5
print(d.popleft())
#Output: 0
print(d.pop())
#Output: 4
print(d)
#Output: deque([1, 2, 3])
d = deque(range(5))
d.extendleft([1])
d.extend([6,7,8])
print(d)
#Output: deque([1, 0, 1, 2, 3, 4, 6, 7, 8])
namedtuple
)非常像。主要相似点是都不像列表,不能修改元组中的数据。为了获取元组中的数据,需要使用整数作为索引:man = ('Ali', 30)
print(man[0])
#Output: Ali
namedtuples
就是把元组变成一个针对简单任务的容器。不必使用整数索引来访问一个namedtuples
的数据。可以像字典(dict
)一样访问namedtuples
, 但 namedtuples
是不可变的。
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name='perry', age=31, type='cat')
print(perry, perry.name, perry[0])
#Output: (Animal(name='perry', age=31, type='cat'), 'perry', 'perry')
可以看到,可以用名字来访问namedtuple
中的数据。一个命名元组(namedtuple)有两个必须的参数1.
元组名称 2.
字段名称。上面的例子中,元组名称为Animal
, 字段名称为name
, age
, type
。namedtuple
让元组变的自文档, 只要看一眼就很容易理解代码是做什么的。不必使用整数索引来访问一个命名元组,让代码更容易维护。而且, namedtuple的每个实例没有对象字典, 所以它们很轻量,与普通元组比,并不需要更多内存,使得比字典快。
namedtuple
是一个元组,属性值是不可变的。应该使用命名元组让代码自文档,它们向后兼容于普通元组,既可以整数索引,也可以使用名称来访问namedtuple
。如下可以把命名元组转换成字典:
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name='perry', age=31, type='cat')
dic = perry._asdict()
dic.setdefault('gender', 'male')
print(dic)
#Output: OrderedDict([('name', 'perry'), ('age', 31), ('type', 'cat'), ('gender', 'male')])
enum
模块,存在于Python 3.4
以上版本中(同时作为一个独立的PyPI
包enum34
供老版本使用)。Enums
(枚举类型)基本上是一种组织各种东西的方式。上面Animal
命名元组有个type
字段,但是type
是字符串。万一输入Cat
, CAT
, kitten
就会出错。枚举可以通过不使用字符串避免这个问题:# -*- coding: UTF-8 -*-
#!usr/bin/env python3
from collections import namedtuple
from enum import Enum
class Species(Enum):
cat = 1
dog = 2
horse = 3
aardvark = 4
butterfly = 5
owl = 6
platypus = 7
dragon = 8
unicorn = 9
# 依次类推
# 我们不关心同一物种的年龄,所以我们可以使用一个别名:w
kitten = 1
puppy = 2
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type=Species.cat)
drogon = Animal(name="Drogon", age=4, type=Species.dragon)
tom = Animal(name="Tome", age=75, type=Species.cat)
charlie = Animal(name="Charlie", age=2, type=Species.kitten)
print(charlie.type, charlie.type == tom.type)
#Output: Species.cat True
print(Species(1), Species['cat'], Species.cat)
#Output: Species.cat Species.cat Species.cat
自省(introspection), 在计算机编程领域里,是指在运行时判断一个对象类型的能力。它是Python
的强项之一。Python
中所有一切都是对象,而且我们可以仔细勘察那些对象。Python
还包含许多内置函数和模块来帮助我们。
my_list = [1, 2, 3]
print(dir(my_list))
#Output:
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__delslice__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__',
'__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__',
'__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']
上面自省给我们一个列表所有方法和名字。当你没法回忆起一个方法的名字,这回非常有帮助。如果我们运行dir()
而不传入参数,那么它会返回当前作用域的名字。
type
函数返回一个对象的类型。id
函数返回任意不同种类对象的唯一ID
:print(type(''))
#Output: <type 'str'>
print(type([]))
#Output: <type 'list'>
print(type({}))
#Output: <type 'dict'>
print(dict)
#Output: <type 'dict'>
print(type(3))
#Output: <type 'int'>
name = "Yasoob"
print(id(name))
#Output: 4439591360
inspect
模块提供很多有用的函数来获取活跃对象的信息。比如,可以查看一个对象的成员:import inspect
print(inspect.getmembers(str))
推导式(又称解析式)是Python
一种独有特性,可以从一个数据序列构建另一个新的数据序列的结构体。共3
种推导:
for
,然后是0个
或多个for
或者if
语句。那个表达式可以是任意的,意思是可以在列表种放入任意类型的对象。返回结果将是一个新的列表,在这个以if
和for
语句为上下文的表达式运行完成后产生。# -*- coding: UTF-8 -*-
#!usr/bin/env python3
# 规范:variable = [out_exp for out_exp in input_list if out_exp == 2]
multiples = [i for i in range(30) if (i%3) is 0]
print(multiples)
#Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
squared = [x**2 for x in range(10)]
print(squared)
#Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
这对快速生成列表非常有用。有些人甚至更喜欢使用它而不是filter
函数。
# -*- coding: UTF-8 -*-
#!usr/bin/env python3
mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
mcase_frequency = {
k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0)
for k in mcase.keys()
}
print(mcase_frequency)
#Output: {'a': 17, 'z': 3, 'b': 34}
# 还可以快速对换一个字典的键和值
# {v: k for k, v in some_dict.items()}
{}
):# -*- coding: UTF-8 -*-
#!usr/bin/env python3
squared = {x**2 for x in [1, 1, 2]}
print(squared)
#Output:set([1, 4])
异常处理是一种艺术,最基本的术语就是我们知道的try/except
从句。可能会触发异常产生的代码放到try
中,而处理异常的代码会放在except
语句块里实现:
try:
file = open('test.txt', 'rb')
except IOError as e:
print('An IOError Occurred. {}'.format(e.args[-1]))
#Output: An IOError Occurred. No such file or directory
我们可以使用3
种方法来处理多个异常:
try:
file = open('test.txt', 'rb')
except (IOError, EOFError) as e:
print("An error occurred. {}".format(e.args[-1]))
except
语句块种处理try:
file = open('test.txt', 'rb')
except EOFError as e:
print("An EOF error occurred.")
raise e
except IOError as e:
print("An error occurred.")
raise e
如果异常没有被第一个except
语句块处理,那么会被下一个语句块处理,或者根本不会被处理。
try:
file = open('test.txt', 'rb')
except Exception:
print("An error occurred.")
raise
我们把主程序代码包裹在try
从句中,然后我们把一些代码包裹在except
从句中,他会在try
中的代码触发异常时会执行。包裹在finally
从句中的代码不管异常是否触发都会被执行。可以被用来在脚本执行之后做清理工作:
try:
file = open('test.txt', 'rb')
except IOError as e:
print('An IOError occurred. {}'.format(e.args[-1]))
finally:
print("This would be printed whether or not an exception occurred!")
# Ooutput: An IOError occurred. No such file or directory
# This would be printed whether or not an exception occurred!
我们常想在没有触发异常时执行一些代码。可以通过一个else
从句来达到。不直接把代码放在try
为了避免任意异常都会被try
捕获。
# -*- coding: UTF-8 -*-
#!usr/bin/env python3
try:
print('I am sure no exception is going to occur!')
except Exception:
print('exception')
else:
# 这里的代码会在`try`语句没有触发异常时运行
# 但这里的异常不会被捕获
print('This would only run if no exceptino occurs. And an error here '
'would NOT be caught.')
finally:
print('This would be printed in every case.')
#Output: I am sure no exception is going to occur!
# This would only run if no exceptino occurs. And an error here would NOT be caught.
# This would be printed in every case.
lambda
表达式是一行函数。在其他语言中也被称为匿名函数。如果不想在程序中对一个函数使用两次,可以使用lambda
表达式,和普通函数完全一样。
# -*- coding: UTF-8 -*-
#!usr/bin/env python3
# 原型: lambda 参数:操作(参数)
add = lambda x, y: x + y
print(add(3, 5))
# Output: 8
# 列表排序
a = [(1, 2), (4, 1), (9, 10), (13, -3)]
a.sort(key=lambda x: x[1])
print(a)
# Output: [(13, -3), (4, 1), (1, 2), (9, 10)]
# 列表并行排序
list1 = [1, 7, 5]
list2 = [2, 8, 3]
data = zip(list1, list2)
data = sorted(data)
list1, list2 = map(lambda t: list(t), zip(*data))
print(list1, list2)
#Output: ([1, 5, 7], [2, 3, 8])
Python
提供通过网络快速共享文件的功能。进入要共享文件的目录下并在命令行中运行下面的代码: python -m SimpleHTTPServer
Python REPL
漂亮的打印出列表和字典.# -*- coding: UTF-8 -*-
#!usr/bin/env python3
from pprint import pprint
import json
my_dict = {'name': 'Yasoob', 'age': 'undefined', 'personality': 'awesome'}
pprint(my_dict)
with open('file.json', 'w') as openedfile:
openedfile.write(json.dumps(my_dict))
openedfile.close()
以下命令可以快速的从文件打印出json
数据:
cat file.json | python -m json.tool
脚本性能分析可能定位脚本中的性能瓶颈:
python -m cProfile 463.py
循环是任何语言都有的。for
循环就是Python
的一个重要组成部分。
fruits = ['apple', 'banana', 'mango']
for fruit in fruits:
print(fruit.capitalize())
# Output: Apple
# Banana
# Mango
for
循环还有一个else
从句, 这个else
从句会在循环正常结束时执行。这意味着,循环没有遇到任何break
. 一旦掌握何时何地使用它,会非常有用。有个常见的例子是: 跑一个循环,并查找一个元素。如果这个元素找到可以使用break
来中断这个循环。有两个场景会让循环停下来:
break
被触发else
从句:findKey = 8
some_list = [1, 2, 3, 4, 5, 6]
for val in some_list:
if val == findKey:
print("Found it")
break
else:
print("Didn't find anything")
#Output: Didn't find anything
以下例子可以找出2
到10
之间数字的因子。我们附加上else
语句块,可以判断出质数:
for n in range(2, 10):
for x in range(2, n):
if n%x == 0:
print(n, 'equals', x, '*', n/x)
break
else:
print(n, 'is a prime number')
#Output:
'''
(2, 'is a prime number')
(3, 'is a prime number')
(4, 'equals', 2, '*', 2)
(5, 'is a prime number')
(6, 'equals', 2, '*', 3)
(7, 'is a prime number')
(8, 'equals', 2, '*', 4)
(9, 'equals', 3, '*', 3)
'''
CPtyon
还为开发者实现一个有趣的特性,使用Python
可以轻松调用C
代码。开发者有三种方法可以在自己的Python
代码中调用C
编写的函数 ctypes
, SWIG
, Python/C API
。每种方式都各自有利弊。我们在Python
中调用C
的常见原因:
C
要比Python
快50
倍以上C
语言中有很多传统类库,而且有些正是你想要的,但又不想用Python
重写Python
中的https://docs.python.org/2/library/ctypes.html模块可能是Python
调用C
方法中最简单的一种。ctypes
模块提供了和C
语言兼容的数据类型和函数来加载dll
文件,因此在调用时不需对源文件做任何修改。例如: 实现两数求和的C
代码,保存为add.c
:
#include <stdio.h>
int add_int(int, int);
float add_float(float, float);
int add_int(int num1, int num2) {
return num1 + num2;
}
float add_float(float num1, float num2) {
return num1 + num2;
}
将
C
文件编译为.so
文件: $ gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c
# -*- coding: UTF-8 -*-
#!usr/bin/env python3
from ctypes import *
adder = CDLL('./adder.so')
res_int = adder.add_int(4, 5)
print("Sum of 4 and 5 = " + str(res_int))
#Output: Sum of 4 and 5 = 9
a = c_float(5.5)
b = c_float(4.1)
add_float = adder.add_float
add_float.restype = c_float
print("Sum of 5.5 and 4.1 = ", str(add_float(a, b)))
#Output: ('Sum of 5.5 and 4.1 = ', '9.60000038147')
这个例子中,C
文件是自解释的,它包含两个函数,分别实现了整型求和和浮点型求和。在Python
文件中,一开始先导入ctypes
模块,然后使用CDLL
函数来加载我们创建的库文件。这样我们就可以通过变量adder
来使用C
类库中的函数。当adder.add_int()
被调用时,内部将发起一个对C
函数add_int
的调用。ctypes
接口允许我们在调用C
函数时使用原生Python
中默认的字符串和整型。
而对于其他类似布尔值和浮点型,必须使用正确的ctype
类型才可以。如像adder.add_float()
函数传参时,我们要先将Python
中的十进制值转化为c_float
类型,然后才能传送给C
函数。这种方法虽然简单,清晰,但却很受限。例如,不能操作C
中对象。
SWIG
是Simplified Wrapper and Interface Generator
的缩写。是Python
中调用C
代码的另一种方法。这个方法中,开发人员必须编写一个额外的接口文件来作为SWIG
(终端工具)的入口。Python
开发者一般不会采用这种方法,因为大多数情况它会带来不必要的复杂。而当你有一个C/C++
代码库需要被多种语言调用时,这会是个不错的选择。
Python/C API可能是被最广泛使用的方法。它不仅简单,而且可以在C
代码中操作你的Python
对象。这种方法需要以特定的方式来编写C
代码以供Python
去调用它。所有的Python
对象都被表示为一种叫做PyObject
的结构体,并且Python.h
头文件中提供了各种操作它的函数。例如, 如果PyObject
表示为PyListType
(列表类型)时,那么我们便可以使用PyList_Size()
函数来获取该结构的长度,类似Python
中len(list)
函数。大部分对Python
原生对象的基础函数和操作在Python.h
头文件中都能找到。
示例:编写一个
C
扩展,添加所有元素到一个Python
列表(所有元素都是数字)
//Python.h has all the required function definitions to manipulate the Python objects
#include <Python.h>
//This is the function that is called from your python code
static PyObject* addList_add(PyObject* self, PyObject* args) {
PyObject* listObj;
//The input arguments come as a tuple, we parse the args to
// get the varipous variables In this case it's only one list
// variable, which will now be referenced by listObj
if (!PyArg_ParseTuple(args, "O", &listObj))
return NULL;
//length of the list
long length = PyList_Size(listObj);
//iterate over all the elements
int i, sum = 0;
for (i = 0;i < length; i++) {
// get an element out of the list - the element is also a python objects
PyObject* temp = PyList_GetItem(listObj, i);
// we know that object represents an integer - so convert it into C long
long elem = PyInt_AsLong(temp);
sum += elem;
}
//value returned back to python code - another python object
//build value here converts the C long to a python integer
return Py_BuildValue("i", sum);
}
// This is the docstring that corresponds to our 'add' function
static char addList_docs[] = "add(): add all elements of the list\n";
/* This table contains the relavent info mapping -
* <function>-name in python module>, <actual-function>
* <type-of-args the function expects>, <docstring associated with the function>
*/
static PyMethodDef addList_funcs[] = {
{"add", (PyCFunction)addList_add, METH_VARARGS, addlist_docs},
{NULL, NULL, 0, NULL}
};
/* addList is the module name, and this is the initialization block of the module
* <desired module name>, <the-info-table>, <module's-docstring>
*/
PyMODINIT_FUNC initaddList(void) {
Py_InitModule3("addList", addLIst_funcs, "Add all ze lists");
}
Python.h
头文件中包含了所有需要的类型(Python
对象类型的表示)和函数定义(对Python
对象的操作)Python
调用的函数,函数传统的命名方式由{模块名}_{函数名}组成,所以我们将其命名为addList_add
PyMODINIT_FUNC init{模块名}
函数
addList_add
接受的参数类型为PyObject
类型结构(同时也表示为元组类型,因为Python
中万物皆对象,所以我们先用PyObject
来定义)。传入的参数则通过PyArg_ParseTuple()
来解析。第一个参数是被解析的参数变量。第二个参数是一个字符串,告诉我们如何去解析元组中每个元素。字符串的第n
个字母正是代表元组中第n
个参数类型。例如: “i”代表整型, “s”代表字符串,“O”则代表一个Python
对象。接下来的参数都是你想要通过PyArg_ParseTuple()
函数解析并保存的元素。这样参数的数量和模块中函数期待得到的参数数量就可以保持一致,并保证了位置的完整性。例如,我们想传入一个字符串,一个整数和一个Python
列表,可以:
int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "siO", &n, &s, &list);
这种情况下,我们只需要提取一个列表对象,并将它存储在listObj
变量中。然后用列表对象中的PyList_Size()
函数来获取它的长度。就像Python
中调用len(list)
。现在我们通过循环列表,使用PyList_GetItem(list, index)
函数来获取每个元素。这将返回一个PyObject*
对象。既然Python
对象也能表示PyInType
,我们只要使用PyInt_AsLong(PyObj *)
函数便可获得我们所需要的值。我们对每个元素都这样处理,最后再得到他们的总和。总和将被转化为一个Python
对象并通过Py_BuildValue()
返回给Python
代码,这里的i
表示我们要返回一个Python
整型对象。现在我们已经编写完C
模块。将下列代码保存为setup.py
, 将我们的C
文件编译安装到我们的Python
模块中。
# -*- coding: UTF-8 -*-
#!usr/bin/env python3
#build the modules
from distutils.core import setup, Extension
setup(name='addList', version='1.0', \
ext_modules=[Extension('addList', ['adder.c'])])
#Run: python setup.py install
Python
调用C
代码的另一种方式便是使用Cython让Python
编译的更快。但是Cython
和传统的Python
比起来可以将它理解为另一种语言。
open函数可以打开一个文件:
f = open('phone.jpg', 'r+')
jgpdata = f.read()
f.close()
有三个错误存在于上面的代码中。open
的返回值是一个文件句柄,但前提是只有在read
成功的情况下。如果有任何异常正好在 f = open(...)
之后产生,f.close()
将不会被调用(取决于Python
解释器的做法,文件句柄可能还会被归还)。为了确保不管异常是否触发,文件都能关闭,我们将其包裹成一个with
语句:
with open('photo.jpg', 'r+') as f:
jpgdata = f.read()
open
的第一个参数是文件名。第二个(mode
代开模式)决定了这个文件如何被打开。
r
r+
w
a
虽然有若干个其他有效的mode
字符串,但有可能你将永远不会使用他们。mode
很重要,不仅因为它改变了行为,而且可能导致权限错误。比如:我们要是在一个写保护的目录里打开一个jpg
文件,open(.., 'r+')
就失败了。 mode
可能包含一个扩展字符;让我们还可以以二进制的方式打开文件(你将得到字符串)或文本模式(字符串)。
一般来说,如果文件格式是由人写的,那它更可能是文本模式。jpg
图像文件一般不是人写的(而且不是人直接可读的), 因此应该以二进制模式打开它,方式是在mode
字符串后加一个b
。如果你以文本模式打开一些东西(比如,加一个t
, 或者用 r/r+/w/a
),你还必须知道要使用那种编码。对于计算机来说,所有文件都是字节,而不是字符。
可惜,在Python2.x
版本里,open
不支持显示地指定编码。然而,io.open
函数在Python2.x
中和Python3.x
中都提供,io.open
能做正确的事。你可以传入encoding
这个关键字参数来传入编码。如果不传入任意编码,一个系统-以及Python
-指定的默认选项将被选中。你也许被诱惑去依赖这个默认选项,但这个默认选项经常是错误的,或者默认编码实际上不能表达文件里的所有字符(这经常发生在Python2.x
和Windows
)。所以去挑选一个编码吧, utf-8
是一个非常好的编码。
不幸的是,并没有一个十分简单的方式来检测正在读的文件的编码。在不同的编码中,同样的字节可以表示不同,但同样有效的字符。因此,必须依赖一个元数据(比如,在HTTP
头信息里)来找出编码。越来越多的文件格式将编码定义为UTF-8
。 重写上面open
:
import io
with open('photo.JPG', 'rb') as inf:
jpgdata = inf.read()
if jpgdata.startswith(b'\xff\xd8'): # JPG 文件头部以字节 FFD8 开始
text = u'This is a JPEG file (%d bytes logn)\n'
else:
text = u'This is a random file (%d bytes long)\n'
with io.open('summary.txt', 'w', encoding='utf-8') as outf:
outf.write(text % len(jpgdata))
很多时候可能希望开发的程序能够同时兼容Python2+
和Python3+
。有些技巧可以让脚本同时兼容Python2
和Python3
。
上下文管理器是Python2.6+
引入的新特性,如果你想在Python2.5
中使用它可以这样做:
from __future__ import with_statement
在Python3
中print
已经变为一个函数。如果想在Python2
中使用它可以通过__future__
导入:
from __future__ import print_function
print(print)
#Output: <built-in function print>
我们在脚本中导入模块,大多时候会这样做:
import foo
# or
from foo import bar
# 其实也可以这样做
import foo as foo
这样做可以起到和上面代码同样的功能,但最重要的是它能让你的脚本同时兼容Python2
和Python3
。现在看下如下代码:
try:
import urllib.request as urllib_request # for Python3
except ImportError:
import urllib2 as urllib_request # for Python2
print(urllib_request)
#Output: <module 'urllib.request' from '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py'>
# <module 'urllib2' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.pyc'>
我们将模块导入代码包装在 try/except
语句中。我们这样做是因为Python2
中并没有urllib.request
模块。这将引起一个ImportError
异常。而在Python2
中urllib.request
的功能则是由urllib2
提供的。所以,当我们试图在Python2
中导入urllib.request
模块时,一旦我们捕获到ImportError
将通过urllib2
模块来代替它。
最后,要了解as
关键字的作用。它将导入的模块映射到urllib.request
, 所以我们通过urllib_request
这个别名就可以使用urllib2
中所有类和方法。
有一些包在非官方的支持下为Python2
提供了Python3
的功能。例如,我们有:
Python
中的协程和生成器很相似但又稍有不同。主要区别在于:
首先我们先来回顾下生成器的创建过程。我们可以这样去创建一个生成器:
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
for i in fib():
print(i)
这样做不仅快而且不会给内存带来压力,因为我们所需要的值都是动态生成的而不是将他们存储在一个列表中。更概括的说如果现在我们在上面的例子中使用yield
便可获得一个协程。协程会消费掉发送给它的值。Python
实现grep
就是个很好的例子:
def grep(pattern):
print("Searching for", pattern)
while True:
line = (yield)
if pattern in line:
print(line)
search = grep('coroutine')
next(search)
#Output: ('Searching for', 'coroutine')
search.send("I love you")
search.send("Don't you love me?")
search.send("I love coroutine instead!")
#Output: I love coroutine instead!
grep
中yield返回了什么?我们已经把它变成一个协程。它将不再包含任何初始值,相反要从外部传值给它。 我们可以通过send()
方法向他传值。发送的值会被yield
接受。我们为什么要运行next()
方法?这样做是为了启动一个协程。就像协程中包含的生成器并不是立刻执行,而是通过next()
方法来响应send()
方法。因此,你必须通过next()
方法来执行yield
表达式,我们可以通过调用close()
方法来关闭一个协程:
search = grep('coroutine')
search.close()
def countdown(n):
print "Counting down from", n
while (n > 0):
yield n
n -= 1
#for i in countdown(5):
# print(i)
x = countdown(10)
print(x)
#Output: <generator object countdown at 0x10c959a00>
print(x.next())
#OUTPUT: Counting down from 10
# 10
print(x.next())
#OUTPUT: 8
print(x.next())
#OUTPUT: 8
print(x.next())
print(x.next())
print(x.next())
print(x.next())
print(x.next())
print(x.next())
print(x.next())
print(x.next())
#Traceback (most recent call last):
# File "463.py", line 31, in <module>
# print(x.next())
#StopIteration
更多协程相关参考 http://www.dabeaz.com/coroutines/Coroutines.pdf
函数缓存允许我们将一个函数对于给定参数的返回值缓存起来。当一个I/O
密集的函数被频繁使用相同的参数调用时,函数缓存可以节约时间。在Python3.2
版本以前我们只有写一个自定义的实现。在Python3.2
以后版本,有个lru_cache
的装饰器,允许我们将一个函数的返回值快速的缓存或取消缓存:
# -*- coding: UTF-8 -*-
#!usr/bin/env python3
from functools import lru_cache
# maxsize 参数告诉 lru_cache, 最多缓存最近多少个返回值
@lru_cache(maxsize=32)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print([fib(n) for n in range(10)])
# Output:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
fib.cache_clear()
from functools import wraps
def memoize(function):
memo = {}
@wraps(function)
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(25))
#Output: 75025
上下文管理器允许你在有需要时,精确的分配和释放资源。使用上下文管理器最广泛的例子就是with
语句:
# -*- coding: UTF-8 -*-
#!usr/bin/env python3
with open("summary.txt", "w") as opened_file:
opened_file.write("Hola!")
# 上面这段代码打开一个文件,往里写入一些数据,然后关闭文件
# 如果在往文件写数据时发生异常,它也会尝试去关闭文件
# 等价于下面这段代码:
file = open("summary.txt", "w")
try:
file.write("Hola!")
finally:
file.close()
当与第一个例子对比时,我们可以看到,通过使用with
, 许多样板代码(boilerplate code)被消掉了。这就是with
语句的主要优势,它确保我们的文件会被关闭,而不用关注嵌套代码如何退出。 上下文管理器的一个常见用例,是资源加锁和解锁,以及关闭已打开的文件。让我们看看如何来实现我们自己的上下文管理器。这会让我们更完全明白在这些场景背后都发生着什么:
一个上下文管理器的类,最起码要定义__enter__
和__exit__
方法:
class File(object):
def __init__(self, file_name, method):
print("== init ==")
self.file_obj = open(file_name, method)
def __enter__(self):
print("== enter ==")
return self.file_obj
def __exit__(self, type, value , traceback):
print("== exit ==")
self.file_obj.close()
with File("summary.txt", "w") as opened_file:
opened_file.write("Hola!")
#Output: == init ==
# == enter ==
# == exit ==
我们的__exit__
函数接受三个参数。这些参数对于每个上下文管理器类中的__exit__
方法都是必须的。在底层到底发生了什么:
with
语句先暂存了File
类的__exit__
方法File
类的__enter__
方法__enter__
方法打开文件并返回给with
语句opened_file
参数.write()
来写文件with
语句调用之前暂存的__exit__
方法__exit__
方法关闭文件我们还没有谈到__exit__
方法的这三个参数:type
, value
和 traceback
。
在第4
步和第6
步之间,如果发生异常,Python
会将异常的type
, value
和 trackback
传递给__exit__
方法。它让__exit__
方法来决定如何关闭文件以及是否需要其他步骤。在我们的案例中,我们并没有注意他们。
那如果我们的文件对象抛出一个异常呢?万一我们尝试访问文件对象的一个不支持的方法;
class File(object):
def __init__(self, file_name, method):
print("== init ==")
self.file_obj = open(file_name, method)
def __enter__(self):
print("== enter ==")
return self.file_obj
def __exit__(self, type, value , traceback):
print("== exit ==")
self.file_obj.close()
with File("summary.txt", "w") as opened_file:
opened_file.undefined_function('Hola!')
我们来列一下,当异常发生时,with
语句会采取那些步骤:
type
, value
和trackback
传递给__exit__
方法__exit__
方法来处理异常__exit__
返回的是True
, 那么这个异常就被优雅的处理了__exit__
返回的是True
意外的任何东西,那么这个异常将被with
语句抛出在我们的例子中, __exit__
方法返回的是None
(如果没有return
语句那么方法会返回None
)。因此,with
语句抛出异常。我们尝试在__exit__
方法中处理异常:
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value , traceback):
print("Exception has been handled")
self.file_obj.close()
return True
with File("summary.txt", "w") as opened_file:
opened_file.undefined_function('Hola!')
我们的__exit__
方法返回了True
, 因此没有异常会被with
语句抛出。
我们还可以用装饰器(decorators
)和生成器(generators
)来实现上下文管理器。Python
有个contextlib
模块专门用于这个目的。我们可以使用一个生成器函数来实现一个上下文管理器,而不是使用一个类。看下例子:
from contextlib import contextmanager
@contextmanager
def open_file(name):
f = open(name, 'w')
yield f
f.close()
with open_file('summary.txt') as f:
f.write("Test")
这个实现方式看起来更加直观和简单。然而,这个方法需要关于生成器,yield
和装饰器的一些知识。在这个例子中我们还没有捕捉可能产生的任何异常。它的工作方式和之前的方法大致相同:
Python
解释器遇到了yield
关键字。因为这个缘故它创建了一个生成器而不是一个普通函数contextmanager
会被调用并传入函数名(open_file
)作为参数contextmanager
函数返回一个以GeneratorContextManager
对象封装过的生成器GeneratorContextManager
被赋值给open_file
函数,我们实际上是在调用GeneratorContextManager
对象