飞跃式发展的后现代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。举个例子,我们可以实现对代数数据类型的衡量:

  1. from macropy.case_classes import case
  2.  
  3. @case
  4. class Nil():
  5. pass
  6.  
  7. @case
  8. class Cons(x, xs):
  9. pass
  10.  
  11. Cons(1, Cons(2, Cons(3, Nil())))

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

  1. def reduce(op, my_list):
  2. with switch(my_list):
  3. if Cons(x, Nil()):
  4. return x
  5. elif Cons(x, xs):
  6. return op(x, reduce(op, xs))

  消失的部分仍然是一个沿着camlp4路线,可扩展阶段的元编程系统。但是 Mython提供了一个pgen2解析框架,给引用块定义了新的语法,来解决这个问题。

  1. my[namedtupledef] Point(x, y): pass
  2.  
  3. my[c]:
  4. int add (int x, int y) {
  5. return x + y;
  6. }
  7.  
  8. print "Regular Python"

 类型

  Python 是动态类型语言,并且引以为傲。我当然不希望对类型的“圣战”煽风点火,但同时肯定有大学派认为构建可靠的应用程序需要有比只使用单元测试更加有力的保障。Benjamin Pierce对类型系统的定义如下:

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

  重点是证明有关运行空间的属性, 所有程序行为的运行空间替代了只是简单地罗列有限种情况的运行空间。全静态类型对于Python是否是正确的选择让人十分疑惑,但是在过度的动态类型和静态类型保证之间肯定有更加合适的方案。MyPy project找到了一个不错的平衡点,允许有类型的和没有类型的代码能够同时存于语言的超集中。例如:

  1. def simple_typed(x : int, y : int) -> int:
  2. return x + y
  3.  
  4. simple_typed(1, 2) # Type-checks succesfully
  5.  
  6. # Fails: Argument 2 to "simple_typed" has incompatible type # "float"
  7. simple_typed(1, 2.0)
  8.  
  9. # Fails: Argument 2 to "simple_typed" has incompatible type "str"
  10. simple_typed(1, "foo")

  当然对C语言没有太多的用处。所以我们不只限于简单类型的函数,参数类型也有泛型,指针类型和各种各样内建的类型级的函数。

  1. from typing import Iterator, typevar, Generic, Function, List
  2.  
  3. T = typevar('T')
  4.  
  5. def example_typed(x : Iterator[int]) -> Iterator[str]:
  6. for i in x:
  7. yield str(i)
  8.  
  9. def example_generic(x : Iterator[T]) -> Iterator[T]:
  10. for i in x:
  11. yield i

  我们也能定义更加高级的泛型结构例如函子和单元

  1. a = typevar('a')
  2. b = typevar('b')
  3.  
  4. class Functor(Generic[a]):
  5. def __init__(self, xs : List[a]) -> None:
  6. self._storage = xs
  7.  
  8. def iter(self) -> Iterator[a]:
  9. return iter(self._storage)
  10.  
  11. def fmap(f : Function[[a], b], xs : Functor[a]) -> Functor[b]:
  12. return Functor([f(x) for x in xs.iter()])
  13.  
  14. class Monad(Generic[a]):
  15. def __init__(self, val : a) -> None:
  16. self.val = val
  17.  
  18. class IdMonad(Monad):
  19.  
  20. # Monad m => a -> m a
  21. def unit(self, x : a) -> Monad[b]:
  22. return IdMonad(x)
  23.  
  24. # Monad m => m a -> (a -> m b) -> m b
  25. def bind(self, x : Monad[a], f : Function[[a], Monad[b]]) -> Monad[b]:
  26. return f(x.val)
  27.  
  28. # Monad m => m (m a) -> m a
  29. def join(self, x : Monad[Monad[a]]) -> Monad[a]:
  30. return x.val

 速度

  “高性能”Python最近最重要的进展是Pandas库提供的更高等级DataFrame容器的开发。Pandas混合各种Python进行操作,对于某些操作使用NumPy,其它的使用Cython,对于某些内部哈希表甚至使用C语言。Panda底层架构非教条式的方法已经让它成为数据分析领域的标准库。Pandas的开发体现了很多让数值Python生态系统成功的东西。

  1. In [1]: from pandas import DataFrame
  2.  
  3. In [2]: titanic = DataFrame.from_csv('titanic.csv')
  4.  
  5. In [3]: titanic.groupby('pclass').survived.mean()
  6. pclass
  7. 1st 0.619195
  8. 2nd 0.429603
  9. 3rd 0.255289
  10. Name: survived

  然而改善Python性能最近的尝试是利用LLVM编译器有选择的编译某些Python代码段为本地代码。虽然不同的技术的实现方式不同,但是大部分与下述方式类似:

  1. 在函数上添加@jit或@compile这样的装饰器。
  2. 函数的AST或者bytecode被提取出来放入编译器流水线,在流水线中被映射到内部AST,给定特定的输入类型集合决定如何将给定的函数逻辑降低为机器代码。
  3. 编译过的函数与一组类型一起被调用,参数被检查过,代码在给定类型下生成。生成的代码连同参数被缓存使得接下来的调用直接分发到本地代码。

  这些项目增加了大家对Python语言技术和llvmpy项目开发的兴趣,我猜测llvmpy在Python的历史上比特定的JIT编译器更重要。

  最简单的例子(来自极好的Kaleidescope教程)是创建一个简单的本地乘加函数,然后通过解箱三个Python整数调用它:

  1. import llvm.core as lc
  2. import llvm.ee as le
  3.  
  4. mod = lc.Module.new('mymodule')
  5.  
  6. i32 = lc.Type.int(32)
  7. funty = lc.Type.function(lc.Type.int(), [i32, i32, i32])
  8.  
  9. madd = lc.Function.new(mod, funty, "multiply")
  10. x = madd.args[0]
  11. y = madd.args[1]
  12. z = madd.args[2]
  13.  
  14. block = madd.append_basic_block("L1")
  15.  
  16. builder = lc.Builder.new(block)
  17. x0 = builder.mul(x, y)
  18. x1 = builder.add(x0, z)
  19.  
  20. builder.ret(x1)
  21.  
  22. print mod
  23.  
  24. tm = le.TargetMachine.new(features='', cm=le.CM_JITDEFAULT)
  25. eb = le.EngineBuilder.new(mod)
  26. engine = eb.create(tm)
  27.  
  28. ax = le.GenericValue.int(i32, 1024)
  29. ay = le.GenericValue.int(i32, 1024)
  30. az = le.GenericValue.int(i32, 1024)
  31.  
  32. ret = engine.run_function(madd, [ax, ay, az])
  33.  
  34. print ret.as_int()
  35. print mod.to_native_assembly()

上述代码编译生成下述LLVM IR。

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

  虽然这个例子不太直观,但是可以生成很快的JIT'd函数,与NumPy这样的库集成的很好,把数据做为大块的解箱内存存储。

 接口

  分解行为到可组合的单元,而不是显式的继承层次结构是一个Python没有解决好的问题,经常导致噩梦般的复杂的使用mixin。然而通过使用ABC模组模仿静态定义的接口可以缓解这个问题。

  1. import heapq
  2. import collections
  3.  
  4. class Heap(collections.Sized):
  5. def __init__(self, initial=None, key=lambda x:x):
  6. self.key = key
  7. if initial:
  8. self._data = [(key(item), item) for item in initial]
  9. heapq.heapify(self._data)
  10. else:
  11. self._data = []
  12.  
  13. def pop(self):
  14. return heapq.heappop(self._data)[1]
  15.  
  16. def push(self, item):
  17. heapq.heappush(self._data, (self.key(item), item))
  18.  
  19. def len(self):
  20. return len(self._data)

  例如建立一个等价类,让所有类的实例实现eq()方法。我们可以这样做::

  1. from abc import ABCMeta, abstractmethod
  2.  
  3. class Eq(object):
  4.  
  5. __metaclass__ = ABCMeta
  6.  
  7. @classmethod
  8. def __subclasshook__(cls, C):
  9. if cls is Eq:
  10. for B in C.__mro__:
  11. if "eq" in B.__dict__:
  12. if B.__dict__["eq"]:
  13. return True
  14. break
  15. return NotImplemented
  16.  
  17. def eq(a, b):
  18. if isinstance(a, Eq) and isinstance(b, Eq) and type(a) == type(b):
  19. return a.eq(b)
  20. else:
  21. raise NotImplementedError
  22.  
  23. class Foo(object):
  24. def eq(self, other):
  25. return True
  26.  
  27. class Fizz(Foo):
  28. pass
  29.  
  30. class Bar(object):
  31. def __init__(self, val):
  32. self.val = val
  33.  
  34. def eq(self, other):
  35. return self.val == other.val
  36.  
  37. print eq(Foo(), Foo())
  38. print eq(Bar(1), Bar(1))
  39. print eq(Foo(), Bar(1))
  40. print eq(Foo(), Fizz())

  然后扩展这种类型的接口概念到多参数的函数,使得查询__dict__越来越可能发生,在组合的情况下很脆弱。问题的关键是分解所有的事情到单一类型不同的接口,当我们真正想要的是声明涵盖一组多类型的接口时。OOP中的这种缺点是 表达式问题的关键。

  诸如Scala、Haskell和Rust这样的语言以trait和typeclass这样的形式提供该问题的解决方案。例如Haskell可以自动地为所有类型的交叉产品推导出微分方程。

  1. instance (Floating a, Eq a) => Floating (Dif a) where
  2. pi = C pi
  3.  
  4. exp (C x) = C (exp x)
  5. exp (D x x') = r where r = D (exp x) (x' * r)
  6.  
  7. log (C x) = C (log x)
  8. log p@(D x x') = D (log x) (x' / p)
  9.  
  10. sqrt (C x) = C (sqrt x)
  11. sqrt (D x x') = r where r = D (sqrt x) (x' / (2 * r))

 异步编程

  在这个主题下,我们还是有很多缝缝补补的解决方案,解决了部分的问题,但是引入了一整与常规Python背道而驰的套限制和模式。Gevent通过剪接底层C堆栈保持了Python自己的一致性。生成的API非常优雅,但是使得推理控制流和异常非常复杂。

  1. import gevent
  2.  
  3. def foo():
  4. print('Running in foo')
  5. gevent.sleep(0)
  6. print('Explicit context switch to foo again')
  7.  
  8. def bar():
  9. print('Explicit context to bar')
  10. gevent.sleep(0)
  11. print('Implicit context switch back to bar')
  12.  
  13. gevent.joinall([
  14. gevent.spawn(foo),
  15. gevent.spawn(bar),
  16. ]) 

  控制流展示在下面:

  通过对标准库相当不优美的缝缝补补(monkey-patching),我们可以模仿Erlang式带有异步进入点和内部状态的actor行为:

  1. import gevent
  2. from gevent.queue import Queue
  3. from SimpleXMLRPCServer import SimpleXMLRPCServer
  4.  
  5. class Actor(object):
  6. _export = [
  7. 'push',
  8. ]
  9.  
  10. def __init__(self, address):
  11. self.queue = Queue()
  12.  
  13. self._serv = SimpleXMLRPCServer(address, allow_none=True, logRequests=False)
  14. self.address = address
  15.  
  16. for name in self._export:
  17. self._serv.register_function(getattr(self, name))
  18.  
  19. def push(self, thing):
  20. self.queue.put(thing)
  21.  
  22. def poll(self):
  23. while True:
  24. print(self.queue.get())
  25.  
  26. def periodic(self):
  27. while True:
  28. print('PING')
  29. gevent.sleep(5)
  30.  
  31. def serve_forever(self):
  32. gevent.spawn(self.periodic)
  33. gevent.spawn(self.poll)
  34. self._serv.serve_forever()
  35.  
  36. def main():
  37. from gevent.monkey import patch_all
  38. patch_all()
  39.  
  40. serve = Actor(('', 8000))
  41. serve.serve_forever()

 DSLs

  Z3工程是嵌在Python对象层的扩展API。用Z3的实例来解决N皇后问题可以被描述为Python表达式和扩展SMT来解决问题:

  1. from Z3 import *
  2.  
  3. Q = [ Int('Q_%i' % (i + 1)) for i in range(8) ]
  4.  
  5. # Each queen is in a column {1, ... 8 }
  6. val_c = [ And(1 <= Q[i], Q[i] <= 8) for i in range(8) ]
  7. # At most one queen per column
  8. col_c = [ Distinct(Q) ]
  9.  
  10. # Diagonal constraint
  11. diag_c = [ If(i == j,
  12. True,
  13. And(Q[i] - Q[j] != i - j, Q[i] - Q[j] != j - i))
  14. for i in range(8) for j in range(i) ]
  15.  
  16. solve(val_c + col_c + diag_c)

  在Theano,SymPy,PySpark中的其它工程大量使用基于Python表达式的重载操作符的方式。

  1. from sympy import Symbol
  2. from sympy.logic.inference import satisfiable
  3.  
  4. x = Symbol('x')
  5. y = Symbol('y')
  6. satisfiable((x | y) & (x | ~y) & (~x | y))

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

  1. python世界里的局部变量和全局变量: 潜规则太重要了!!!

    python世界里的局部变量和全局变量: 潜规则太重要了!!! 先上代码: def fun(): def test_global(): ''' 内层和外层都需要声明为global, 才能彻底打通变量名 ...

  2. 欢迎来到Python世界

    Python是一门优雅而健壮的解释型编程语言,它具有如下的特点: 易学  Python关键字少.结构简单.语法清晰.学习者可以在相对更短的时间内轻松上手. 易读  Python没有其它语言通常用来访问 ...

  3. 18式优雅你的Python

    本文来自读者梁云同学的投稿,公众号:Python与算法之美 一,优雅你的Jupyter 1,更改Jupyter Notebook初始工作路径 平凡方法: 在cmd中输入jupyter notebook ...

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

    什么是Python Python的起源 Python的特点 下载Python 安装Python 运行Python Python文档 比较Python 其他实现   1.什么是Python        ...

  5. 【Python千问 2】Python核心编程(第二版)-- 欢迎来到Python世界

    1.1 什么是Python 继承了传统编译语言的强大性和通用性,同时也借鉴了简单脚本和解释语言的易用性. 1.2 起源 来源于某个项目,那些程序员利用手边现有的工具辛苦工作着,他们设想并开发了更好的解 ...

  6. Python世界里的赋值运算符

    Python赋值运算符 以下假设变量a为10,变量b为20: "=" 的作用是把右边的数值赋值给左边的变量 示例1:编程实现145893秒是几天几小时几分钟几秒钟? total = ...

  7. 进入python世界

    最近python一直很火,现在已经排名第三了.由于生来害怕蛇,我对python一直不敢接触,突破不了内心的恐惧.但是他太火了,我也无法对他无动于衷了. python是一种动态解释型的语言,而且还有胶水 ...

  8. 初进python世界之数据类型

    文章来源: https://www.cnblogs.com/seagullunix/articles/7297946.html 基本运算符 常用数据类型: 字符串(Str) 数字(Digit) 列表( ...

  9. 一句话打印'*'图案(列表推导式, 人生苦短, 我用Python)

    ```python # coding=utf-8 print ('\n'.join(['*'*6 for i in range(4)])) # ****** # ****** # ****** # * ...

随机推荐

  1. ubuntu安装spyder和jupyter notebook

    ubuntu安装spyder和jupyter notebook 安装spyder 安装spyder sudo apt install spyder sudo apt install spyder3 安 ...

  2. Linux 入门视频教程

    http://v.youku.com/v_show/id_XNzM4NTU0MjQ4.html?f=28697585&o=1 1.1.1 Linux系统简介-UNIX发展历史和发行版本http ...

  3. 可遇不可求的Question之Mysql在不重启服务的情况下修改运行时变量篇

    比方说在一些实际生产环境中,想改个MYSQL的配置,但是又不想停止服务重起MYSQL,有什么办法呢?使用SET命令可以做到,请看下面几个例子: 1.设置key_buffer_size的大小为10M. ...

  4. C#使用Dotfuscator混淆代码的加密方法

    C#编写的代码如果不进行一定程度的混淆和加密,那么是非常容易被反编译进行破解的,特别是对于一些商业用途的C#软件来说,因为盯着的人多,更是极易被攻破.使用VS自带的Dotfuscator可以实现混淆代 ...

  5. STM32的SWD调试进不了main函数

    玩了那么久STM32,还没有用SWD调试过程序(一直都是用printf调试程序),觉得有些落后了,于是开始搞起了SWD调试. 很快通过查阅资料,知道了keil里面的配置和ST-Link与STM32的连 ...

  6. Python爬取淘宝店铺和评论

    1 安装开发需要的一些库 (1) 安装mysql 的驱动:在Windows上按win+r输入cmd打开命令行,输入命令pip install pymysql,回车即可. (2) 安装自动化测试的驱动s ...

  7. 基本数据类型的包装类(Interger)

    基本数据类型 vs包装类 byte Byte short Short char Character int Integer long Long float Float double Double bo ...

  8. gogs 安装

    docker 安装gogs 准备工作 安装一个mysql数据库,创建一个数据库 gogs,字符集为utf-8 查找gogs 镜像 docker search gogs 拉取镜像到本地 docker p ...

  9. Linux下Oracle数据库的安装

    记录详细过程以备使用 一.准备安装 为了确保Oracle数据库11g能够成功安装,您需要做好准备工作,例如检查网络配置.更改Linux内核参数.创建用户Oracle.创建安装目录.设置用户Oracle ...

  10. 背水一战 Windows 10 (107) - 通知(Toast): 提示音, 特定场景

    [源码下载] 背水一战 Windows 10 (107) - 通知(Toast): 提示音, 特定场景 作者:webabcd 介绍背水一战 Windows 10 之 通知(Toast) 提示音 特定场 ...