defpig_latin(text):
''' Takes in a sequence of words and converts it to (imperfect) pig latin. '''
word_list = text.split(' ')
output_list = []
for word in word_list:
word = word.lower()
if word.isalpha():
first_char = word[0]
if first_char in'aeiou':
word = word + 'ay'else:
word = word[1:] + first_char + 'yay'
output_list.append(word)
pygged = ' '.join(output_list)
return pygged
# Add a few more things
my_box_of_things += ['bathing suit', 'bowling ball', 'clarinet', 'ring']
# Maybe add one last thing
my_box_of_things.append('radio that only needs a fuse')
# Let's see what we have...print(my_box_of_things)
大多数 Python 包实际上是用 C 语言编写的,而不是用 Python 编写的。对于大多数标准库,当你调用一个 Python 函数时,实际上很大可能你是在运行具有 Python 接口的 C 代码。这意味着无论你解决问题的算法有多精妙,如果你完全用 Python 编写,而内置的解决方案是用 C 语言编写的,那你的性能可能不如内置的方案。
例如,以下是运行内置的 sum 函数(用 C 编写):
# Create a list of random floatsimport random
my_list = [random.random() for i inrange(10000)]
# Python's built-in sum() function is pretty fast
%timeit sum(my_list)
输出结果约为 47.7 µs ± 4.5 µs per loop。
从算法上来说,你没有太多办法来加速任意数值列表的加和计算。所以你可能会想这是什么鬼,你也许可以用 Python 自己写加和函数,也许这样可以封装内置 sum 函数的开销,以防它进行任何内部验证。嗯……并非如此。
defill_write_my_own_sum_thank_you_very_much(l):
s = 0for elem in my_list:
s += elem
return s
%timeit ill_write_my_own_sum_thank_you_very_much(my_list)
import numpy as np
my_arr = np.array(my_list)
%timeit np.sum(my_arr)
输出结果约为 7.92 µs ± 1.15 µs per loop。
因此简单地切换到 NumPy 可加快一个数量级的列表加和速度,而不需要自己去实现任何东西。
需要更快的速度?
当然,有时候即使使用所有基于 C 的扩展包和高度优化的实现,你现有的 Python 代码也无法快速削减时间。在这种情况下,你的下意识反应可能是放弃并转化到一个「真正」的语言。并且通常,这是一种完全合理的本能。但是在你开始使用 C 或 Java 移植代码前,你需要考虑一些不那么费力的方法。
使用 Cython 编写 C 代码
首先,你可以尝试编写 Cython 代码。Cython 是 Python 的一个超集(superset),它允许你将(某些)C 代码直接嵌入到 Python 代码中。Cython 不以编译的方式运行,相反你的 Python 文件(或其中特定的某部分)将在运行前被编译为 C 代码。实际的结果是你可以继续编写看起来几乎完全和 Python 一样的代码,但仍然可以从 C 代码的合理引入中获得性能提升。特别是简单地提供 C 类型的声明通常可以显著提高性能。
以下是我们简单加和代码的 Cython 版本:
# Jupyter extension that allows us to run Cython cell magics
%load_ext Cython
The Cython extension is already loaded. To reload it, use:
%reload_ext Cython
%%cython
def ill_write_my_own_cython_sum_thank_you_very_much(arr):
cdef int N = len(arr)
cdef float x = arr[0]
cdef int i
for i in range(1, N):
x += arr[i]
return x
%timeit ill_write_my_own_cython_sum_thank_you_very_much(my_list)
输出结果约为 227 µs ± 48.4 µs per loop。
关于 Cython 版本有几点需要注意一下。首先,在你第一次执行定义该方法的单元时,需要很少的(但值得注意的)时间来编译。那是因为,与纯粹的 Python 不同,代码在执行时不是逐行解译的;相反,Cython 式的函数必须先编译成 C 代码才能调用。
import numpy as np
defmultiply_randomly_naive(l):
n = l.shape[0]
result = np.zeros(shape=n)
for i inrange(n):
ind = np.random.randint(0, n)
result[i] = l[i] * l[ind]
return np.sum(result)
%timeit multiply_randomly_naive(my_arr)
defmultiply_randomly_vectorized(l):
n = len(l)
inds = np.random.randint(0, n, size=n)
result = l * l[inds]
return np.sum(result)
%timeit multiply_randomly_vectorized(my_arr)
import numpy as np
from numba import jit
@jitdefmultiply_randomly_naive_jit(l):
n = l.shape[0]
result = np.zeros(shape=n)
for i inrange(n):
ind = np.random.randint(0, n)
result[i] = l[i] * l[ind]
return np.sum(result)
%timeit multiply_randomly_naive_jit(my_arr)
# Create a new Brain instance
brain = Brain(owner="Sue", age="62", status="hanging out in a jar")
print(brain.owner)
# Output: Sueprint(brain.gender)
# Raises AttributeError
举个简单的例子,我们来实现一个表示单一 Nifti 容积的新类。我们将依靠继承来实现大部分工作;只需从 nibabel 包中继承 Nifti1Image 类。我们要做的就是定义 and 和 or 方法,它们分别映射到 & 和 | 运算符。看看在执行以下几个单元前你是否搞懂了这段代码的作用(可能你需要安装一些包,如 nibabel 和 nilearn)。
from nibabel import Nifti1Image
from nilearn.image import new_img_like
from nilearn.plotting import plot_stat_map
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
classLazyMask(Nifti1Image):
''' A wrapper for the Nifti1Image class that overloads the & and | operators
to do logical conjunction and disjunction on the image data. '''def__and__(self, other):
ifself.shape != other.shape:
raise ValueError("Mismatch in image dimensions: %s vs. %s" % (self.shape, other.shape))
data = np.logical_and(self.get_data(), other.get_data())
return new_img_like(self, data, self.affine)
def__or__(self, other):
ifself.shape != other.shape:
raise ValueError("Mismatch in image dimensions: %s vs. %s" % (self.shape, other.shape))
data = np.logical_or(self.get_data(), other.get_data())
return new_img_like(self, data, self.affine)
img1 = LazyMask.load('image1.nii.gz')
img2 = LazyMask.load('image2.nii.gz')
result = img1 & img2
Python 社区
我在这里提到的 Python 的最后一个特征就是它优秀的社区。当然,每种主要的编程语言都有一个大型的社区致力于该语言的开发、应用和推广;关键是社区内的人是谁。一般来说,围绕编程语言的社区更能反映用户的兴趣和专业基础。对于像 R 和 Matlab 这样相对特定领域的语言来说,这意味着为语言贡献新工具的人中很大一部分不是软件开发人员,更可能是统计学家、工程师和科学家等等。当然,统计学家和工程师没什么不好。例如,与其他语言相比,统计学家较多的 R 生态系统的优势之一就是 R 具有一系列统计软件包。
然而,由统计或科学背景用户所主导的社区存在缺点,即这些用户通常未受过软件开发方面的训练。因此,他们编写的代码质量往往比较低(从软件的角度看)。专业的软件工程师普遍采用的最佳实践和习惯在这种未经培训的社区中并不出众。例如,CRAN 提供的许多 R 包缺少类似自动化测试的东西——除了最小的 Python 软件包之外,这几乎是闻所未闻的。另外在风格上,R 和 Matlab 程序员编写的代码往往在人与人之间的一致性方面要低一些。结果是,在其他条件相同的情况下,用 Python 编写软件往往比用 R 编写的代码具备更高的稳健性。虽然 Python 的这种优势无疑与语言本身的内在特征无关(一个人可以使用任何语言(包括 R、Matlab 等)编写出极高质量的代码),但仍然存在这样的情况,强调共同惯例和最佳实践规范的开发人员社区往往会使大家编写出更清晰、更规范、更高质量的代码。