飛躍式發展的後現代 Python 世界

飛躍式發展的後現代Python世界

  若是現代Python有一個標誌性特性,那麼簡單說來即是Python對自身定義的愈來愈模糊。在過去的幾年的許多項目都極大拓展了Python,並重建了「Python」自己的意義。php

  與此同時新技術的涌現侵佔了Python的份額,並帶來了新的優點:html

  1. Go - ( Goroutines, Types, Interfaces )
  2. Rust - ( Traits, Speed, Types )
  3. Julia - ( Speed, Types, Multiple Dispatch )
  4. Scala - ( Traits, Speed, Types )
  5. Clojure ( Metaprogramming, DSLs, Protocols )

  這是一篇Python對這些新技術、新庫及模型響應的簡短指南:python

 元編程

  MacroPy 是一個元編程框架,它提供了多種語法結構,將現代語言元素編譯成標準的Python代碼,擴展了Python AST。舉個例子,咱們能夠實現對代數數據類型的衡量:git

from macropy.case_classes import case

@case
class Nil():
    pass

@case
class Cons(x, xs):
    pass

Cons(1, Cons(2, Cons(3, Nil())))

而後模式和聲明的類型相匹配了:github

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解析框架,給引用塊定義了新的語法,來解決這個問題。編程

my[namedtupledef] Point(x, y): pass

my[c]:
    int add (int x, int y) {
        return x + y;
    }

print "Regular Python"

 類型

  Python 是動態類型語言,而且引覺得傲。我固然不但願對類型的「聖戰」煽風點火,但同時確定有大學派認爲構建可靠的應用程序須要有比只使用單元測試更加有力的保障。Benjamin Pierce對類型系統的定義以下:api

...一種易於處理的語法,經過根據計算值的類型對詞組分類證實了缺乏了特定的程序行爲緩存

  重點是證實有關運行空間的屬性, 全部程序行爲的運行空間替代了只是簡單地羅列有限種狀況的運行空間。全靜態類型對於Python是不是正確的選擇讓人十分疑惑,可是在過分的動態類型和靜態類型保證之間確定有更加合適的方案。MyPy project找到了一個不錯的平衡點,容許有類型的和沒有類型的代碼可以同時存於語言的超集中。例如:架構

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語言沒有太多的用處。因此咱們不僅限於簡單類型的函數,參數類型也有泛型,指針類型和各類各樣內建的類型級的函數。app

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

  咱們也能定義更加高級的泛型結構例如函子和單元

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生態系統成功的東西。

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代碼段爲本地代碼。雖然不一樣的技術的實現方式不一樣,可是大部分與下述方式相似:

  1. 在函數上添加@jit或@compile這樣的裝飾器。
  2. 函數的AST或者bytecode被提取出來放入編譯器流水線,在流水線中被映射到內部AST,給定特定的輸入類型集合決定如何將給定的函數邏輯下降爲機器代碼。
  3. 編譯過的函數與一組類型一塊兒被調用,參數被檢查過,代碼在給定類型下生成。生成的代碼連同參數被緩存使得接下來的調用直接分發到本地代碼。

  這些項目增長了你們對Python語言技術和llvmpy項目開發的興趣,我猜想llvmpy在Python的歷史上比特定的JIT編譯器更重要。

  最簡單的例子(來自極好的Kaleidescope教程)是建立一個簡單的本地乘加函數,而後經過解箱三個Python整數調用它:

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.Function.new(mod, funty, "multiply")
x = madd.args[0]
y = madd.args[1]
z = madd.args[2]

block = madd.append_basic_block("L1")

builder = lc.Builder.new(block)
x0 = builder.mul(x, y)
x1 = builder.add(x0, z)

builder.ret(x1)

print mod

tm = le.TargetMachine.new(features='', cm=le.CM_JITDEFAULT)
eb = le.EngineBuilder.new(mod)
engine = eb.create(tm)

ax = le.GenericValue.int(i32, 1024)
ay = le.GenericValue.int(i32, 1024)
az = le.GenericValue.int(i32, 1024)

ret = engine.run_function(madd, [ax, ay, az])

print ret.as_int()
print mod.to_native_assembly()

上述代碼編譯生成下述LLVM IR。

define i32 @multiply(i32, i32, i32) {
L1:
  %3 = mul i32 %0, %1
  %4 = add i32 %3, %2
  ret i32 %4
}

  雖然這個例子不太直觀,可是能夠生成很快的JIT'd函數,與NumPy這樣的庫集成的很好,把數據作爲大塊的解箱內存存儲。

 接口

  分解行爲到可組合的單元,而不是顯式的繼承層次結構是一個Python沒有解決好的問題,常常致使噩夢般的複雜的使用mixin。然而經過使用ABC模組模仿靜態定義的接口能夠緩解這個問題。

import heapq
import collections

class Heap(collections.Sized):
   def __init__(self, initial=None, key=lambda x:x):
       self.key = key
       if initial:
           self._data = [(key(item), item) for item in initial]
           heapq.heapify(self._data)
       else:
           self._data = []

   def pop(self):
       return heapq.heappop(self._data)[1]

   def push(self, item):
       heapq.heappush(self._data, (self.key(item), item))

   def len(self):
       return len(self._data)

  例如創建一個等價類,讓全部類的實例實現eq()方法。咱們能夠這樣作::

from abc import ABCMeta, abstractmethod

class Eq(object):

    __metaclass__ = ABCMeta

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Eq:
            for B in C.__mro__:
                if "eq" in B.__dict__:
                    if B.__dict__["eq"]:
                        return True
                    break
        return NotImplemented

def eq(a, b):
    if isinstance(a, Eq) and isinstance(b, Eq) and type(a) == type(b):
        return a.eq(b)
    else:
        raise NotImplementedError

class Foo(object):
    def eq(self, other):
        return True

class Fizz(Foo):
    pass

class Bar(object):
    def __init__(self, val):
        self.val = val

    def eq(self, other):
        return self.val == other.val

print eq(Foo(), Foo())
print eq(Bar(1), Bar(1))
print eq(Foo(), Bar(1))
print eq(Foo(), Fizz())

  而後擴展這種類型的接口概念到多參數的函數,使得查詢__dict__愈來愈可能發生,在組合的狀況下很脆弱。問題的關鍵是分解全部的事情到單一類型不一樣的接口,當咱們真正想要的是聲明涵蓋一組多類型的接口時。OOP中的這種缺點是 表達式問題的關鍵。

  諸如Scala、Haskell和Rust這樣的語言以trait和typeclass這樣的形式提供該問題的解決方案。例如Haskell能夠自動地爲全部類型的交叉產品推導出微分方程。

instance (Floating a, Eq a) => Floating (Dif a) where
    pi               = C pi

    exp (C x)        = C (exp x)
    exp (D x x')     = r where r = D (exp x) (x' * r)

    log (C x)        = C (log x)
    log p@(D x x')   = D (log x) (x' / p)

    sqrt (C x)       = C (sqrt x)
    sqrt (D x x')    = r where r = D (sqrt x) (x' / (2 * r))

 異步編程

  在這個主題下,咱們仍是有不少縫縫補補的解決方案,解決了部分的問題,可是引入了一整與常規Python背道而馳的套限制和模式。Gevent經過剪接底層C堆棧保持了Python本身的一致性。生成的API很是優雅,可是使得推理控制流和異常很是複雜。

import gevent

def foo():
    print('Running in foo')
    gevent.sleep(0)
    print('Explicit context switch to foo again')

def bar():
    print('Explicit context to bar')
    gevent.sleep(0)
    print('Implicit context switch back to bar')

gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
]) 

  控制流展現在下面:

  經過對標準庫至關不優美的縫縫補補(monkey-patching),咱們能夠模仿Erlang式帶有異步進入點和內部狀態的actor行爲:

import gevent
from gevent.queue import Queue
from SimpleXMLRPCServer import SimpleXMLRPCServer

class Actor(object):
    _export = [
        'push',
    ]

    def __init__(self, address):
        self.queue = Queue()

        self._serv = SimpleXMLRPCServer(address, allow_none=True, logRequests=False)
        self.address = address

        for name in self._export:
            self._serv.register_function(getattr(self, name))

    def push(self, thing):
        self.queue.put(thing)

    def poll(self):
        while True:
            print(self.queue.get())

    def periodic(self):
        while True:
            print('PING')
            gevent.sleep(5)

    def serve_forever(self):
        gevent.spawn(self.periodic)
        gevent.spawn(self.poll)
        self._serv.serve_forever()

def main():
    from gevent.monkey import patch_all
    patch_all()

    serve = Actor(('', 8000))
    serve.serve_forever()

 DSLs

  Z3工程是嵌在Python對象層的擴展API。用Z3的實例來解決N皇后問題能夠被描述爲Python表達式和擴展SMT來解決問題:

from Z3 import *

Q = [ Int('Q_%i' % (i + 1)) for i in range(8) ]

# Each queen is in a column {1, ... 8 }
val_c = [ And(1 <= Q[i], Q[i] <= 8) for i in range(8) ]
# At most one queen per column
col_c = [ Distinct(Q) ]

# Diagonal constraint
diag_c = [ If(i == j, 
              True, 
              And(Q[i] - Q[j] != i - j, Q[i] - Q[j] != j - i)) 
           for i in range(8) for j in range(i) ]

solve(val_c + col_c + diag_c)

  在Theano,SymPy,PySpark中的其它工程大量使用基於Python表達式的重載操做符的方式。

from sympy import Symbol
from sympy.logic.inference import satisfiable

x = Symbol('x')
y = Symbol('y')
satisfiable((x | y) & (x | ~y) & (~x | y))
相關文章
相關標籤/搜索