Fancy ML Fancy ML
  • RoadMap
  • 数理基础

    • 开始上手
  • Python

    • 开始上手
  • 教程

    • 开始上手
    • 斯坦福大学2014(吴恩达)机器学习教程
深度学习
  • Q&A
关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)

Jonsam NG

让有意义的事变得有意思,让有意思的事变得有意义
  • RoadMap
  • 数理基础

    • 开始上手
  • Python

    • 开始上手
  • 教程

    • 开始上手
    • 斯坦福大学2014(吴恩达)机器学习教程
深度学习
  • Q&A
关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)
  • 开始上手
  • Plan 计划
  • Roadmap 路线
  • RoadMap 机器学习路线
  • 数理基础

  • Python

    • 开始上手
    • IntermediatePython

      • 开始上手
      • Python 进阶 —— 第一节
      • Python 进阶 —— 第二节
      • Python 进阶 —— 第三节
        • 虚拟环境(virtualenv)
        • 容器(Collections)
        • 枚举
        • 对象自省
        • 各种推导式(comprehensions)
      • Python 进阶 —— 第四节
      • Python 进阶 —— 第五节
  • 基础
  • Python
  • IntermediatePython
jonsam
2022-04-13
目录

Python 进阶 —— 第三节

# 虚拟环境(virtualenv)

# 你听说过virtualenv吗?

如果你是一位初学者,你可能没有听说过virtualenv;但如果你是位经验丰富的程序员,那么它可能是你的工具集的重要组织部分。

# 那么,什么是virtualenv?

Virtualenv 是一个工具,它能够帮我们创建一个独立(隔离)的Python环境。想象你有一个应用程序,依赖于版本为2的第三方模块,但另一个程序依赖的版本是3,请问你如何使用和开发这些应用程序?

如果你把一切都安装到了/usr/lib/python2.7/site-packages(或者其它平台的标准位置),那很容易出现某个模块被升级而你却不知道的情况。

在另一种情况下,想象你有一个已经开发完成的程序,但是你不想更新它所依赖的第三方模块版本;但你已经开始另一个程序,需要这些第三方模块的版本。

# 用什么方式解决?

使用virtualenv!针对每个程序创建独立(隔离)的Python环境,而不是在全局安装所依赖的模块。

要安装它,只需要在命令行中输入以下命令:

$ pip install virtualenv
1

最重要的命令是:

$ virtualenv myproject
$ source bin/activate
1
2

执行第一个命令在myproject文件夹创建一个隔离的virtualenv环境,第二个命令激活这个隔离的环境(virtualenv)。

在创建virtualenv时,你必须做出决定:这个virtualenv是使用系统全局的模块呢?还是只使用这个virtualenv内的模块。 默认情况下,virtualenv不会使用系统全局模块。

如果你想让你的virtualenv使用系统全局模块,请使用--system-site-packages参数创建你的virtualenv,例如:

virtualenv --system-site-packages mycoolproject
1

使用以下命令可以退出这个virtualenv:

$ deactivate
1

运行之后将恢复使用你系统全局的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)
1
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']
# })
1
2
3
4
5
6

另一种重要的是例子就是:当你在一个字典中对一个键进行嵌套赋值时,如果这个键不存在,会触发keyError异常。 defaultdict允许我们用一个聪明的方式绕过这个问题。 首先我分享一个使用dict触发KeyError的例子,然后提供一个使用defaultdict的解决方案。

问题 :

some_dict = {}
some_dict['colours']['favourite'] = "yellow"

## 异常输出:KeyError: 'colours'
1
2
3
4

解决方案 :

import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"

## 运行正常
1
2
3
4
5
6

你可以用json.dumps打印出some_dict,例如:

import json
print(json.dumps(some_dict))

## 输出: {"colours": {"favourite": "yellow"}}
1
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
##  })
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)
1
2
3

# deque

deque提供了一个双端队列,你可以从头/尾两端添加或删除元素。要想使用它,首先我们要从collections中导入deque模块:

from collections import deque
1

现在,你可以创建一个deque对象。

d = deque()
1

它的用法就像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'
1
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])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们也可以限制这个列表的大小,当超出你设定的限制时,数据会从对队列另一端被挤出去(pop)。 最好的解释是给出一个例子:

d = deque(maxlen=30)
1

现在当你插入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])
1
2
3
4
5
6

# namedtuple

您可能已经熟悉元组。 一个元组是一个不可变的列表,你可以存储一个数据的序列,它和命名元组(namedtuples)非常像,但有几个关键的不同。 主要相似点是都不像列表,你不能修改元组中的数据。为了获取元组中的数据,你需要使用整数作为索引:

man = ('Ali', 30)
print(man[0])

## 输出: Ali
1
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'
1
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
1
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
1
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), ...
1
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)
1
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>
1
2
3
4

这样就没那么容易错误,我们必须更明确,而且我们应该只使用定义后的枚举类型。

有三种方法访问枚举数据,例如以下方法都可以获取到'cat'的值:

Species(1)
Species['cat']
Species.cat
1
2
3

这只是一个快速浏览collections模块的介绍,建议你阅读本文最后的官方文档。

# 枚举

枚举(enumerate)是Python内置函数。它的用处很难在简单的一行中说明,但是大多数的新人,甚至一些高级程序员都没有意识到它。

它允许我们遍历数据并自动计数,

下面是一个例子:

for counter, value in enumerate(some_list):
    print(counter, value)
1
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')
1
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')]
1
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']
1
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'>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

id()函数返回任意不同种类对象的唯一ID,举个例子:

name = "Yasoob"
print(id(name))
# Output: 139972439030304
1
2
3

# inspect模块

inspect模块也提供了许多有用的函数,来获取活跃对象的信息。比方说,你可以查看一个对象的成员,只需运行:

import inspect
print(inspect.getmembers(str))
# Output: [('__add__', <slot wrapper '__add__' of ... ...
1
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]
1

这里是另外一个简明例子:

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]
1
2
3

这将对快速生成列表非常有用。 有些人甚至更喜欢使用它而不是filter函数。 列表推导式在有些情况下超赞,特别是当你需要使用for循环来生成一个新列表。举个例子,你通常会这样做:

squared = []
for x in range(10):
    squared.append(x**2)
1
2
3

你可以使用列表推导式来简化它,就像这样:

squared = [x**2 for x in range(10)]
1

# 字典推导式(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}
1
2
3
4
5
6
7
8

在上面的例子中我们把同一个字母但不同大小写的值合并起来了。

就我个人来说没有大量使用字典推导式。

你还可以快速对换一个字典的键和值:

{v: k for k, v in some_dict.items()}
1

# 集合推导式(set comprehensions)

它们跟列表推导式也是类似的。 唯一的区别在于它们使用大括号{}。 举个例子:

squared = {x**2 for x in [1, 1, 2]}
print(squared)
# Output: {1, 4}
1
2
3
编辑 (opens new window)
上次更新: 2022/04/13, 23:38:30
Python 进阶 —— 第二节
Python 进阶 —— 第四节

← Python 进阶 —— 第二节 Python 进阶 —— 第四节→

最近更新
01
Roadmap 路线
04-16
02
Plan 计划
04-16
03
开始上手
04-13
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Fancy ML | Made by Jonsam by ❤
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式