飞跃式发展的后现代Python世界
如果现代Python有一个标志性特性,那么简单说来便是Python对自身定义的越来越模糊。在过去的几年的许多项目都极大拓展了Python,并重建了“Python”本身的意义。
与此同时新技术的涌现侵占了Python的份额,并带来了新的优势:
- Go – ( Goroutines, Types, Interfaces )
- Rust – ( Traits, Speed, Types )
- Julia – ( Speed, Types, Multiple Dispatch )
- Scala – ( Traits, Speed, Types )
- Clojure ( Metaprogramming, DSLs, Protocols )
这是一篇Python对这些新技术、新库及模型响应的简短指南:
元编程
MacroPy 是一个元编程框架,它提供了多种语法结构,将现代语言元素编译成标准的Python代码,扩展了Python AST。举个例子,我们可以实现对代数数据类型的衡量:
1 2 3 4 5 6 7 8 9 10 11 |
from macropy.case_classes import case @case class Nil(): pass @case class Cons(x, xs): pass Cons(1, Cons(2, Cons(3, Nil()))) |
然后模式和声明的类型相匹配了:
1 2 3 4 5 6 |
def reduce(op, my_list): with switch(my_list): if Cons(x, Nil()): return x elif Cons(x, xs): return op(x, reduce(op, xs)) |
消失的部分仍然是一个沿着camlp4路线,可扩展阶段的元编程系统。但是 Mython提供了一个pgen2解析框架,给引用块定义了新的语法,来解决这个问题。
1 2 3 4 5 6 7 8 |
my[namedtupledef] Point(x, y): pass my[c]: int add (int x, int y) { return x + y; } print "Regular Python" |
类型
Python 是动态类型语言,并且引以为傲。我当然不希望对类型的“圣战”煽风点火,但同时肯定有大学派认为构建可靠的应用程序需要有比只使用单元测试更加有力的保障。Benjamin Pierce对类型系统的定义如下:
…一种易于处理的语法,通过根据计算值的类型对词组分类证明了缺少了特定的程序行为
重点是证明有关运行空间的属性, 所有程序行为的运行空间替代了只是简单地罗列有限种情况的运行空间。全静态类型对于Python是否是正确的选择让人十分疑惑,但是在过度的动态类型和静态类型保证之间肯定有更加合适的方案。MyPy project找到了一个不错的平衡点,允许有类型的和没有类型的代码能够同时存于语言的超集中。例如:
1 2 3 4 5 6 7 8 9 10 |
def simple_typed(x : int, y : int) -> int: return x + y simple_typed(1, 2) # Type-checks succesfully # Fails: Argument 2 to "simple_typed" has incompatible type # "float" simple_typed(1, 2.0) # Fails: Argument 2 to "simple_typed" has incompatible type "str" simple_typed(1, "foo") |
当然对C语言没有太多的用处。所以我们不只限于简单类型的函数,参数类型也有泛型,指针类型和各种各样内建的类型级的函数。
1 2 3 4 5 6 7 8 9 10 11 |
from typing import Iterator, typevar, Generic, Function, List T = typevar('T') def example_typed(x : Iterator[int]) -> Iterator[str]: for i in x: yield str(i) def example_generic(x : Iterator[T]) -> Iterator[T]: for i in x: yield i |
我们也能定义更加高级的泛型结构例如函子和单元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
a = typevar('a') b = typevar('b') class Functor(Generic[a]): def __init__(self, xs : List[a]) -> None: self._storage = xs def iter(self) -> Iterator[a]: return iter(self._storage) def fmap(f : Function[[a], b], xs : Functor[a]) -> Functor[b]: return Functor([f(x) for x in xs.iter()]) class Monad(Generic[a]): def __init__(self, val : a) -> None: self.val = val class IdMonad(Monad): # Monad m => a -> m a def unit(self, x : a) -> Monad[b]: return IdMonad(x) # Monad m => m a -> (a -> m b) -> m b def bind(self, x : Monad[a], f : Function[[a], Monad[b]]) -> Monad[b]: return f(x.val) # Monad m => m (m a) -> m a def join(self, x : Monad[Monad[a]]) -> Monad[a]: return x.val |
速度
“高性能”Python最近最重要的进展是Pandas库提供的更高等级DataFrame容器的开发。Pandas混合各种Python进行操作,对于某些操作使用NumPy,其它的使用Cython,对于某些内部哈希表甚至使用C语言。Panda底层架构非教条式的方法已经让它成为数据分析领域的标准库。Pandas的开发体现了很多让数值Python生态系统成功的东西。
1 2 3 4 5 6 7 8 9 10 |
In [1]: from pandas import DataFrame In [2]: titanic = DataFrame.from_csv('titanic.csv') In [3]: titanic.groupby('pclass').survived.mean() pclass 1st 0.619195 2nd 0.429603 3rd 0.255289 Name: survived |
然而改善Python性能最近的尝试是利用LLVM编译器有选择的编译某些Python代码段为本地代码。虽然不同的技术的实现方式不同,但是大部分与下述方式类似:
- 在函数上添加@jit或@compile这样的装饰器。
- 函数的AST或者bytecode被提取出来放入编译器流水线,在流水线中被映射到内部AST,给定特定的输入类型集合决定如何将给定的函数逻辑降低为机器代码。
- 编译过的函数与一组类型一起被调用,参数被检查过,代码在给定类型下生成。生成的代码连同参数被缓存使得接下来的调用直接分发到本地代码。
这些项目增加了大家对Python语言技术和llvmpy项目开发的兴趣,我猜测llvmpy在Python的历史上比特定的JIT编译器更重要。
最简单的例子(来自极好的Kaleidescope教程)是创建一个简单的本地乘加函数,然后通过解箱三个Python整数调用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import llvm.core as lc import llvm.ee as le mod = lc.Module.new('mymodule') i32 = lc.Type.int(32) funty = lc.Type.function(lc.Type.int(), [i32, i32, i32]) madd = lc. |