使用外部库

在本教程中你将学习 Python 的 导入(imports,获得一些处理不熟悉库(以及它们返回的对象)的小技巧,并深入了解 运算符重载(operator overloading

Imports

到目前为止我们讨论的是语言内置的类型和函数。

但是 Python 的一大优点(特别是对数据科学家)是有大量高质量的第三方库可用。

有些库属于“标准库”,无论在哪里运行 Python 都能找到;
其他库可以很容易地安装,即便默认不会随 Python 一起发布。

无论哪种情况,我们都通过 imports 来访问这些代码。

下面先从标准库里的 math 模块导入做示例。

import math

print("It's math! It has type {}".format(type(math)))

math 是一个模块。
模块只是别人的一组变量(如果你愿意可以叫作*命名空间*)。
我们可以用内置函数 dir() 查看 math 中所有的名字。

import math

print(dir(math))

我们可以用语法访问这些变量。
其中一些是简单的值,比如 math.pi

import math

print("pi to 4 significant digits = {:.4}".format(math.pi))

但模块中大部分是函数,例如 math.log

import math

math.log(32, 2)

当然,如果我们不知道 math.log 是干什么的,可以对它调用 help()

help(math.log)

我们也可以对模块本身调用 help()
这会给出模块中所有函数和值的综合文档(以及模块的概述)。
点击“输出”可以查看完整的 math 帮助页。

help(math)

其他导入语法

如果我们知道会频繁使用 math 中的函数,可以给它取一个短一点的别名以减少敲字量(尽管在这个例子里 “math” 已经很短了)。

import math as mt

print(mt.pi)

你可能见过对某些流行库这样做的习惯,如 Pandas、Numpy、Tensorflow、Matplotlib。
比如常见的约定是 import numpy as npimport pandas as pd

as 只是给导入的模块改了个名。
它等价于下面这种写法:

import math

mt = math

如果我们希望直接使用 math 模块里的变量(即不用写 math.pimt.pi,而直接写 pi),那也可以。

from math import *

print(pi, log(32, 2))

import * 会把模块的所有变量直接导入到当前命名空间(无需点前缀)。

坏消息是:一些代码整洁主义者可能会因此抱怨你。

更糟的是:他们确实有道理。

from math import *
from numpy import *

print(pi, log(32, 2))

发生了什么?
上面原本能工作的代码现在有问题了!

这种“星号导入(star imports)”有时会导致奇怪且难以调试的情况。

本例的问题是 mathnumpy 都有名为 log 的函数,但它们语义不同。
因为我们第二个导入了 numpy,它的 log 会覆盖(或“遮蔽”)从 math 导入的 log

一个折衷的好办法是只从每个模块导入我们需要的具体名字:

from math import log, pi
from numpy import asarray

子模块(Submodules)

我们看到模块包含变量,这些变量可以是函数
还要注意的是,模块也可以包含指向其他模块的变量。

import numpy

print("numpy.random is a", type(numpy.random))
print("it contains names such as...", dir(numpy.random)[-15:])

因此如果我们像上面那样导入 numpy,调用 random “子模块”中的函数就需要两个点。

import numpy
# Roll 10 dice
rolls = numpy.random.randint(low=1, high=6, size=10)
print(rolls)

你会遇到的各种对象

经过前面几课,你应该已经熟悉了 int、float、bool、list、string 和 dict(对吧?)。

不过这还不是全部。随着你使用各种专门库,会发现它们定义了自己的类型,你需要学会与之交互。
例如,图形库 matplotlib 会有代表子图、图形、刻度和注释的对象;
pandas 的函数会产出 DataFrame 和 Series。

在本节中,我想给你一个快速的生存指南,帮助你应对陌生类型。

理解陌生对象的三把工具

在上面的代码中,我们看到调用 numpy 的函数返回了一个“数组(array)”。
我们以前没见过这种类型(至少在本课程里没有),但别慌:我们有三个熟悉的内置函数可以帮忙。

  1. type()(这是什么东西?)
type(rolls)
  1. dir()(我能对它做什么?)
print(dir(rolls))

如果我想要平均掷骰结果,看到有个 mean 方法看起来很有用…

rolls.mean()

或者我只是想把数组变成列表,可以用 tolist

rolls.tolist()
  1. help()(告诉我更多)
help(rolls.ravel)

好吧,既然好奇,告诉我关于 numpy.ndarray 的一切:

help(rolls)

当然,你也可以查看在线文档

运算符重载(Operator overloading)

下面这个表达式的值是多少?

[3, 4, 1, 2, 2, 1] + 10

很显然会报错。

但那下面呢?

rolls + 10

我们可能认为 Python 严格控制其核心语法的行为,例如 +<in==,或者用于索引和切片的方括号。
但实际上,Python 采取了较为放手的策略。当你定义一个新类型时,可以选择如何实现加法,或者定义类型的对象在与另一个对象比较是否相等。

列表的设计者决定不允许把列表与数字相加,而 numpy 数组的设计者则走了另一条路(把数字加入到数组的每个元素上)。

下面还有一些 numpy 数组与 Python 操作符交互的例子(或至少与列表的行为不同的例子)。

# At which indices are the dice less than or equal to 3?
rolls <= 3
import numpy

xlist = [[1,2,3],[2,4,6],]
# Create a 2-dimensional array
x = numpy.asarray(xlist)
print("xlist = {}\nx =\n{}".format(xlist, x))
# Get the last element of the second row of our numpy array
x[1,-1]

# Get the last element of the second sublist of our nested list?
xlist[1,-1]

numpyndarray 类型擅长处理多维数据,因此它为索引定义了自己的逻辑,允许用元组来指定每一维的索引。

什么时候 1 + 1 不等于 2?

情况还能更奇怪。你可能听说过(或使用过) tensorflow,这是一个流行的用于深度学习的 Python 库。
它大量使用了运算符重载。

import tensorflow as tf
# Create two constants, each with value 1
a = tf.constant(1)
b = tf.constant(1)
# Add them together to get...
a + b

a + b 并不是 2,而是(引用 tensorflow 文档):

一个指向某个 Operation 输出的符号句柄。
它不保存该操作输出的值,而是提供在一个 TensorFlow tf.Session 中计算这些值的手段。

重要的是要意识到这种事情是可能的:库经常以不直观或看起来很“魔法”的方式使用运算符重载。

理解 Python 的操作符在 int、string、list 上的行为并不能保证你立刻理解它们在 tensorflow 的 Tensor、numpy 的 ndarray 或 pandas 的 DataFrame 上是如何工作的。

一旦你开始接触 DataFrame,下面这种表达式会变得直观且很受欢迎:

# Get the rows with population over 1m in South America
df[(df['population'] > 10**6) & (df['continent'] == 'South America')]

但它为什么能工作?
上面可能用了大约 5 个不同的被重载的操作符。
每个操作符在背后做了什么?
当问题出现时,知道答案是有帮助的。

想知道它们是如何工作的?

当你对一个对象调用 help()dir() 时,会看到很多以双下划线开始和结束的名字,你可能会想它们是干什么的。

print(dir(list))

这与运算符重载直接相关。

当 Python 程序员想定义他们类型上的运算符行为时,会实现以两个下划线开头和结尾的特殊方法,例如 __lt____setattr____contains__
通常,遵循这种双下划线格式的名字对 Python 有特殊含义。

例如,表达式 x in [1, 2, 3] 实际上在背后调用了列表的方法 __contains__,等价于(看起来更丑的写法)[1, 2, 3].__contains__(x)

如果你想深入了解,可以查看 Python 官方文档,里面描述了很多很多这样的特殊“下划线”方法。

我们在本课程里不会定义自己的类型(要是有时间就好了!),但希望你未来有机会体验定义自己奇妙又古怪类型的乐趣。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐