Python 进阶 —— 第三节
# 虚拟环境(virtualenv)
# 你听说过virtualenv
吗?
如果你是一位初学者,你可能没有听说过virtualenv
;但如果你是位经验丰富的程序员,那么它可能是你的工具集的重要组织部分。
# 那么,什么是virtualenv
?
Virtualenv
是一个工具,它能够帮我们创建一个独立(隔离)的Python环境。想象你有一个应用程序,依赖于版本为2的第三方模块,但另一个程序依赖的版本是3,请问你如何使用和开发这些应用程序?
如果你把一切都安装到了/usr/lib/python2.7/site-packages
(或者其它平台的标准位置),那很容易出现某个模块被升级而你却不知道的情况。
在另一种情况下,想象你有一个已经开发完成的程序,但是你不想更新它所依赖的第三方模块版本;但你已经开始另一个程序,需要这些第三方模块的版本。
# 用什么方式解决?
使用virtualenv
!针对每个程序创建独立(隔离)的Python环境,而不是在全局安装所依赖的模块。
要安装它,只需要在命令行中输入以下命令:
$ pip install virtualenv
最重要的命令是:
$ virtualenv myproject
$ source bin/activate
2
执行第一个命令在myproject
文件夹创建一个隔离的virtualenv环境,第二个命令激活这个隔离的环境(virtualenv
)。
在创建virtualenv时,你必须做出决定:这个virtualenv是使用系统全局的模块呢?还是只使用这个virtualenv内的模块。 默认情况下,virtualenv不会使用系统全局模块。
如果你想让你的virtualenv使用系统全局模块,请使用--system-site-packages
参数创建你的virtualenv,例如:
virtualenv --system-site-packages mycoolproject
使用以下命令可以退出这个virtualenv:
$ deactivate
运行之后将恢复使用你系统全局的Python模块。
# 福利
你可以使用smartcd
来帮助你管理你的环境,当你切换目录时,它可以帮助你激活(activate)和退出(deactivate)你的virtualenv。我已经用了很多次,很喜欢它。你可以在github(https://github.com/cxreg/smartcd) 上找到更多关于它的资料。
这只是一个virtualenv的简短介绍,你可以在 http://docs.python-guide.org/en/latest/dev/virtualenvs/ 找到更多信息。
# 容器(Collections
)
Python附带一个模块,它包含许多容器数据类型,名字叫作collections
。我们将讨论它的作用和用法。
我们将讨论的是:
- defaultdict
- counter
- deque
- namedtuple
- enum.Enum (包含在Python 3.4以上)
# defaultdict
我个人使用defaultdict
较多,与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)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 运行输出
# defaultdict(<type 'list'>,
# {'Arham': ['Green'],
# 'Yasoob': ['Yellow', 'Red'],
# 'Ahmed': ['Silver'],
# 'Ali': ['Blue', 'Black']
# })
2
3
4
5
6
另一种重要的是例子就是:当你在一个字典中对一个键进行嵌套赋值时,如果这个键不存在,会触发keyError
异常。 defaultdict
允许我们用一个聪明的方式绕过这个问题。 首先我分享一个使用dict
触发KeyError
的例子,然后提供一个使用defaultdict
的解决方案。
问题 :
some_dict = {}
some_dict['colours']['favourite'] = "yellow"
## 异常输出:KeyError: 'colours'
2
3
4
解决方案 :
import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"
## 运行正常
2
3
4
5
6
你可以用json.dumps
打印出some_dict
,例如:
import json
print(json.dumps(some_dict))
## 输出: {"colours": {"favourite": "yellow"}}
2
3
4
# counter
Counter是一个计数器,它可以帮助我们针对某项数据进行计数。比如它可以用来计算每个人喜欢多少种颜色:
from collections import Counter
colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)
favs = Counter(name for name, colour in colours)
print(favs)
## 输出:
## Counter({
## 'Yasoob': 2,
## 'Ali': 2,
## 'Arham': 1,
## 'Ahmed': 1
## })
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
我们也可以在利用它统计一个文件,例如:
with open('filename', 'rb') as f:
line_count = Counter(f)
print(line_count)
2
3
# deque
deque提供了一个双端队列,你可以从头/尾两端添加或删除元素。要想使用它,首先我们要从collections
中导入deque
模块:
from collections import deque
现在,你可以创建一个deque
对象。
d = deque()
它的用法就像python的list
,并且提供了类似的方法,例如:
d = deque()
d.append('1')
d.append('2')
d.append('3')
print(len(d))
## 输出: 3
print(d[0])
## 输出: '1'
print(d[-1])
## 输出: '3'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
你可以从两端取出(pop)数据:
d = deque(range(5))
print(len(d))
## 输出: 5
d.popleft()
## 输出: 0
d.pop()
## 输出: 4
print(d)
## 输出: deque([1, 2, 3])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们也可以限制这个列表的大小,当超出你设定的限制时,数据会从对队列另一端被挤出去(pop)。 最好的解释是给出一个例子:
d = deque(maxlen=30)
现在当你插入30条数据时,最左边一端的数据将从队列中删除。
你还可以从任一端扩展这个队列中的数据:
d = deque([1,2,3,4,5])
d.extendleft([0])
d.extend([6,7,8])
print(d)
## 输出: deque([0, 1, 2, 3, 4, 5, 6, 7, 8])
2
3
4
5
6
# namedtuple
您可能已经熟悉元组。
一个元组是一个不可变的列表,你可以存储一个数据的序列,它和命名元组(namedtuples
)非常像,但有几个关键的不同。
主要相似点是都不像列表,你不能修改元组中的数据。为了获取元组中的数据,你需要使用整数作为索引:
man = ('Ali', 30)
print(man[0])
## 输出: Ali
2
3
4
嗯,那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)
## 输出: Animal(name='perry', age=31, type='cat')
print(perry.name)
## 输出: 'perry'
2
3
4
5
6
7
8
9
10
11
12
现在你可以看到,我们可以用名字来访问namedtuple
中的数据。我们再继续分析它。一个命名元组(namedtuple
)有两个必需的参数。它们是元组名称和字段名称。
在上面的例子中,我们的元组名称是Animal
,字段名称是'name','age'和'type'。
namedtuple
让你的元组变得自文档了。你只要看一眼就很容易理解代码是做什么的。
你也不必使用整数索引来访问一个命名元组,这让你的代码更易于维护。
而且, namedtuple
的每个实例没有对象字典 ,所以它们很轻量,与普通的元组比,并不需要更多的内存。这使得它们比字典更快。
然而,要记住它是一个元组,属性值在namedtuple
中是不可变的,所以下面的代码不能工作:
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
perry.age = 42
## 输出:
## Traceback (most recent call last):
## File "", line 1, in
## AttributeError: can't set attribute
2
3
4
5
6
7
8
9
10
你应该使用命名元组来让代码 自文档 , 它们向后兼容于普通的元组 ,这意味着你可以既使用整数索引,也可以使用名称来访问namedtuple
:
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
print(perry[0])
## 输出: perry
2
3
4
5
6
7
最后,你可以将一个命名元组转换为字典,方法如下:
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type="cat")
print(perry._asdict())
## 输出: OrderedDict([('name', 'Perry'), ('age', 31), ...
2
3
4
5
6
7
# enum.Enum (Python 3.4+)
另一个有用的容器是枚举对象,它属于enum
模块,存在于Python 3.4以上版本中(同时作为一个独立的PyPI包enum34
供老版本使用)。Enums(枚举类型)基本上是一种组织各种东西的方式。
让我们回顾一下上一个'Animal'命名元组的例子。
它有一个type字段,问题是,type是一个字符串。
那么问题来了,万一程序员输入了Cat
,因为他按到了Shift键,或者输入了'CAT',甚至'kitten'?
枚举可以帮助我们避免这个问题,通过不使用字符串。考虑以下这个例子:
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
# 依次类推
# 但我们并不想关心同一物种的年龄,所以我们可以使用一个别名
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="Tom", age=75, type=Species.cat)
charlie = Animal(name="Charlie", age=2, type=Species.kitten)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 一些测试
>>> charlie.type == tom.type
True
>>> charlie.type
<Species.cat: 1>
2
3
4
这样就没那么容易错误,我们必须更明确,而且我们应该只使用定义后的枚举类型。
有三种方法访问枚举数据,例如以下方法都可以获取到'cat'的值:
Species(1)
Species['cat']
Species.cat
2
3
这只是一个快速浏览collections
模块的介绍,建议你阅读本文最后的官方文档。
# 枚举
枚举(enumerate
)是Python内置函数。它的用处很难在简单的一行中说明,但是大多数的新人,甚至一些高级程序员都没有意识到它。
它允许我们遍历数据并自动计数,
下面是一个例子:
for counter, value in enumerate(some_list):
print(counter, value)
2
不只如此,enumerate
也接受一些可选参数,这使它更有用。
my_list = ['apple', 'banana', 'grapes', 'pear']
for c, value in enumerate(my_list, 1):
print(c, value)
# 输出:
(1, 'apple')
(2, 'banana')
(3, 'grapes')
(4, 'pear')
2
3
4
5
6
7
8
9
上面这个可选参数允许我们定制从哪个数字开始枚举。 你还可以用来创建包含索引的元组列表, 例如:
my_list = ['apple', 'banana', 'grapes', 'pear']
counter_list = list(enumerate(my_list, 1))
print(counter_list)
# 输出: [(1, 'apple'), (2, 'banana'), (3, 'grapes'), (4, 'pear')]
2
3
4
# 对象自省
自省(introspection),在计算机编程领域里,是指在运行时来判断一个对象的类型的能力。它是Python的强项之一。Python中所有一切都是一个对象,而且我们可以仔细勘察那些对象。Python还包含了许多内置函数和模块来帮助我们。
# dir
在这个小节里我们会学习到dir
以及它在自省方面如何给我们提供便利。
它是用于自省的最重要的函数之一。它返回一个列表,列出了一个对象所拥有的属性和方法。这里是一个例子:
my_list = [1, 2, 3]
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']
2
3
4
5
6
7
8
9
10
上面的自省给了我们一个列表对象的所有方法的名字。当你没法回忆起一个方法的名字,这会非常有帮助。如果我们运行dir()
而不传入参数,那么它会返回当前作用域的所有名字。
# type
和id
type
函数返回一个对象的类型。举个例子:
print(type(''))
# Output: <type 'str'>
print(type([]))
# Output: <type 'list'>
print(type({}))
# Output: <type 'dict'>
print(type(dict))
# Output: <type 'type'>
print(type(3))
# Output: <type 'int'>
2
3
4
5
6
7
8
9
10
11
12
13
14
id()
函数返回任意不同种类对象的唯一ID,举个例子:
name = "Yasoob"
print(id(name))
# Output: 139972439030304
2
3
# inspect
模块
inspect
模块也提供了许多有用的函数,来获取活跃对象的信息。比方说,你可以查看一个对象的成员,只需运行:
import inspect
print(inspect.getmembers(str))
# Output: [('__add__', <slot wrapper '__add__' of ... ...
2
3
还有好多个其他方法也能有助于自省。如果你愿意,你可以去探索它们。
# 各种推导式(comprehensions)
推导式(又称解析式)是Python的一种独有特性,如果我被迫离开了它,我会非常想念。推导式是可以从一个数据序列构建另一个新的数据序列的结构体。 共有三种推导,在Python2和3中都有支持:
- 列表(
list
)推导式 - 字典(
dict
)推导式 - 集合(
set
)推导式
我们将一一进行讨论。一旦你知道了使用列表推导式的诀窍,你就能轻易使用任意一种推导式了。
# 列表推导式(list
comprehensions)
列表推导式(又称列表解析式)提供了一种简明扼要的方法来创建列表。
它的结构是在一个中括号里包含一个表达式,然后是一个for
语句,然后是0个或多个for
或者if
语句。那个表达式可以是任意的,意思是你可以在列表中放入任意类型的对象。返回结果将是一个新的列表,在这个以if
和for
语句为上下文的表达式运行完成之后产生。
# 规范
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]
2
3
这将对快速生成列表非常有用。
有些人甚至更喜欢使用它而不是filter
函数。
列表推导式在有些情况下超赞,特别是当你需要使用for
循环来生成一个新列表。举个例子,你通常会这样做:
squared = []
for x in range(10):
squared.append(x**2)
2
3
你可以使用列表推导式来简化它,就像这样:
squared = [x**2 for x in range(10)]
# 字典推导式(dict
comprehensions)
字典推导和列表推导的使用方法是类似的。这里有个我最近发现的例子:
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()
}
# mcase_frequency == {'a': 17, 'z': 3, 'b': 34}
2
3
4
5
6
7
8
在上面的例子中我们把同一个字母但不同大小写的值合并起来了。
就我个人来说没有大量使用字典推导式。
你还可以快速对换一个字典的键和值:
{v: k for k, v in some_dict.items()}
# 集合推导式(set
comprehensions)
它们跟列表推导式也是类似的。 唯一的区别在于它们使用大括号{}
。 举个例子:
squared = {x**2 for x in [1, 1, 2]}
print(squared)
# Output: {1, 4}
2
3