飞跃式发展的后现代 Python 世界

飞跃式发展的后现代Python世界

  如果现代Python有一个标志性特性,那么简单说来便是Python对自身定义的越来越模糊。在过去的几年的许多项目都极大拓展了Python,并重建了“Python”本身的意义。

  与此同时新技术的涌现侵占了Python的份额,并带来了新的优势:

  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对这些新技术、新库及模型响应的简短指南:

 元编程

  MacroPy 是一个元编程框架,它提供了多种语法结构,将现代语言元素编译成标准的Python代码,扩展了Python AST。举个例子,我们可以实现对代数数据类型的衡量:

from macropy.case_classes import case

@case
class Nil():
    pass

@case
class Cons(x, xs):
    pass

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

然后模式和声明的类型相匹配了:

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对类型系统的定义如下:

...一种易于处理的语法,通过根据计算值的类型对词组分类证明了缺少了特定的程序行为

  重点是证明有关运行空间的属性, 所有程序行为的运行空间替代了只是简单地罗列有限种情况的运行空间。全静态类型对于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语言没有太多的用处。所以我们不只限于简单类型的函数,参数类型也有泛型,指针类型和各种各样内建的类型级的函数。

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 [email protected](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))
时间: 2024-10-10 00:36:37

飞跃式发展的后现代 Python 世界的相关文章

走进Python世界(五)数据类型 2. 字符串(String)

字符串String 使用引号定义的一组可以包含数字,字母,符号(非特殊系统符号)的集合. 如: Strval="This is a test!" Strval1='This is a test!' Strval2="""This is a test""" 三重引号(DocString) Python三重引号允许字符串跨越多行,包括逐字换行符,制表符和其他特殊字符. 三重引号语法由三个连续的单引号或双引号. #!/usr/bin

走进Python世界(一)入门介绍

什么是Python Python是一种解释性,面向对象的,带有动态语义的高级程序设计语言.它能够使你在编程时能够保持一种简洁易懂的风格,不用过多考虑功能实现的. Python的几个重要阶段 CNRI 时期: CNRI 是资助Python发展的重要单位,Python1.5之前的版本都是这段时间内完成的 BeOpen时期:Python作者 Guido van Rossum与BeOpen公司合作,此期间 推出了2个分支,Python1.6 和Python2.0 DC时期:Python作者将Python

走进Python世界(五)数据类型 1.数字(Number)

数字类型 整型 长整型 浮点型 复数型 整型 整数int表示范围 -2^31  ~  2^31-1 长整型 整数long表示范围的范围很大很大,几乎涵盖任意的的整数. 为了区分int和long,需要在整数后面加L或者小写L. 如果超过整数范围而没有添加long的符号,python会隐式地转换成长整型 浮点型 带小数点的数字类型为浮点型 复数类型 我们在初等数学中所了解的那种复数, 分为实部和虚部 在python语言中,定义虚部用j来表示,如: c=3.14j

18式优雅你的Python

本文来自读者梁云同学的投稿,公众号:Python与算法之美 一,优雅你的Jupyter 1,更改Jupyter Notebook初始工作路径 平凡方法: 在cmd中输入jupyter notebook --generate-config,然后找到生成的配置文件jupyter_notebook_config.py,在其中加入一条语句: c.NotebookApp.notebook_dir =  'F:\我的坚果云\PythonFiles'  优雅方法: 新建文本文件,在其中输入以下内容: F: c

走进Python世界(四)基本语法

Python标识符 Python标识符是用来标识一个变量,函数,类,模块或其他对象的名称.一个标识符开始以字母A到Z或a?z或后跟零个或多个字母下划线(_),下划线和数字(0?9). Python中标识符内不允许标点符号,如@,$和%. Python是一种区分大小写的编程语言.因此,Manpower 和manpower在Python中是两个不同的标识符. 这里有Python标识符命名约定: 类名以大写字母以及所有其它标识符以小写字母. 开头单个前导下划线的标识符表示由该标识符约定意思是私有的.

轻松地进入python世界

3 . 注释 comments在python中以‘#’字符hash character开头,一直到这一行的结束.注释可以出现在一行的开始,或者在代码的后边空闲部分.但是不能出现在一个字面字符串string中.在字符串中,‘#’仅仅是一个‘#’字符. # this is the first comment spam = 1 # and this is the second comment # ... and now a third! text = "# This is not a comment

[Python核心编程] 第1章 欢迎来到Python世界

什么是Python Python的起源 Python的特点 下载Python 安装Python 运行Python Python文档 比较Python 其他实现 1.什么是Python Python是一门优雅而健壮的编程语言,它继承了传统编译语言的强大性和通用性,同时也借鉴了简单脚本和解释语言的易用性.它可以帮你完成工作,而且一段时间以后,你还能看明白自己写的这段代码. 2.Python的起源 1989年底,Guido van Rossum始创了Python: 1991年初,Python发布了第一

进入Python世界——Python学习系列之一

安装Python window下载安装Python python官网地址:https://www.python.org/ 进入官网下载python客户端,现最新的python是python3.5.因python2.x与3.x之间还是有一些区别的,且使用上不全兼容,因此本人下载的为python2.5的版本. python 2.x余3.x的区别参考:http://www.cnblogs.com/codingmylife/archive/2010/06/06/1752807.html 本人下载版本地址

走进Python世界(三)变量与运算符

变量 python中的变量和其他语言的变量类似,它是一个可变化的量,存储规定范围的值. 深一层次的说,它其实是一种引用,引用了存在计算机内存某一块区域的值. 变量的命名 变量有字母,下划线和数字组成 不能以数字开头 不能使用关键字作为变量名 变量的赋值 a = 1 :将值1 赋予变量a ld(a):得到变量a引用的内存地址.如当a=1之后,ld(a)则是取得内存中存为1的部分的 内存地址 运算符 赋值运算符 运算符 描述 示例 = 简单的赋值运算符,赋值从右侧操作数左侧操作数 c = a + b