Python和Cython的Go-like功能
项目描述
包 golang 为Python提供类似Go的功能
gpython 是一个支持轻量级线程的Python解释器。
go 创建轻量级线程。
chan 和 select 提供具有Go语义的通道。
func 允许在类外定义方法。
defer 允许在主控制流程中安排清理。
error 和包 errors 提供错误链。
b 和 u 提供确保对象是字节或Unicode的方法。
gimport 允许在Go工作空间中通过完整路径导入Python模块。
包 golang.pyx 提供类似功能用于Cython/nogil。
还提供了额外的包和工具来填补Python/Cython和Go环境之间的其他差距。(点击查看)
GPython
命令 gpython 提供了一个支持通过紧集成 gevent 实现轻量级线程的Python解释器。GPython的标准库与Python标准库的API兼容,但提供轻量级协程代替OS线程,并通过基于 libuv/libev 的IO调度器内部组织IO。因此,程序可以以低廉的成本创建大量的协程,并且像 time、socket、ssl、subprocess 等模块都可以被所有协程同时使用,就像每个协程都是一个完整的OS线程一样。这为在不更改并发模型和现有代码的情况下扩展程序提供了能力。
此外,GPython始终将UTF-8设置为默认编码,并将 go、chan、select 等放入内置命名空间。
协程和通道
go生成一个协程,如果没有激活gevent,则生成线程。可以通过通道在线程或协程之间交换数据。使用 chan.recv、chan.send 和 chan.close 进行通信。nilchan 代表空通道。select 可用于在多个通道上进行多路复用。例如:
ch1 = chan() # synchronous channel ch2 = chan(3) # channel with buffer of size 3 def _(): ch1.send('a') ch2.send('b') go(_) ch1.recv() # will give 'a' ch2.recv_() # will give ('b', True) ch2 = nilchan # rebind ch2 to nil channel _, _rx = select( ch1.recv, # 0 ch1.recv_, # 1 (ch1.send, obj), # 2 ch2.recv, # 3 default, # 4 ) if _ == 0: # _rx is what was received from ch1 ... if _ == 1: # _rx is (rx, ok) of what was received from ch1 ... if _ == 2: # we know obj was sent to ch1 ... if _ == 3: # this case will be never selected because # send/recv on nil channel block forever. ... if _ == 4: # default case ...
默认情况下,chan 创建可以携带任意Python对象的通道。但是,可以通过 chan(dtype=X) 指定通道元素的类型 - 例如,chan(dtype='C.int') 创建一个元素为C整数的通道。chan.nil(X) 创建一个有类型的空通道。Cython/nogil API 解释了如何使用非Python dtypes的通道,除了在Python中使用外,还可以用于Python和nogil世界之间的交互。
方法
func 装饰器允许独立于类定义方法。
例如
@func(MyClass) def my_method(self, ...): ...
将定义 MyClass.my_method()。
func 还可以用于仅函数,例如
@func def my_function(...): ...
Defer / recover / panic
defer 允许在当前函数返回时调度清理操作。它与 try/finally 类似,但不会强制将清理部分放在最后。例如
wc = wcfs.join(zurl) │ wc = wcfs.join(zurl) defer(wc.close) │ try: │ ... ... │ ... ... │ ... ... │ finally: │ wc.close()
如果延迟清理失败,则先前未处理的异常(如果有的话)不会丢失 - 它将使用 (PEP 3134) 连接并包含到traceback输出中,即使在Python2上也是如此。
为了完整性,还有 recover 和 panic,允许以Go风格的错误处理编程,例如
def _(): r = recover() if r is not None: print("recovered. error was: %s" % (r,)) defer(_) ... panic("aaa")
但是,recover 和 panic 可能不太有用,因为它们可以用 try/except 本地建模。
如果使用 defer,则使用它的函数必须用 @func 装饰器包装。
错误
在并发系统中,操作堆栈通常与执行代码流程不同,这使得代码堆栈跟踪对理解错误来说非常有用。Pygolang提供了错误链支持,可以构建操作错误堆栈并检查结果错误。
error 是一种可以单独使用或被继承的错误类型。通过提供 .Unwrap() 方法,错误可以可选地包装另一个错误,从而形成一个错误链。使用 errors.Is 可以判断错误链中的某个项是否匹配目标。使用 fmt.Errorf 提供了一种方便的方式来构建包装错误。例如
e1 = error("problem") e2 = fmt.Errorf("doing something for %s: %w", "joe", e1) print(e2) # prints "doing something for joe: problem" errors.Is(e2, e1) # gives True # OpError is example class to represents an error of operation op(path). class OpError(error): def __init__(e, op, path, err): e.op = op e.path = path e.err = err # .Error() should be used to define what error's string is. # it is automatically used by error to also provide both .__str__ and .__repr__. def Error(e): return "%s %s: %s" % (e.op, e.path, e.err) # provided .Unwrap() indicates that this error is chained. def Unwrap(e): return e.err mye = OpError("read", "file.txt", io.ErrUnexpectedEOF) print(mye) # prints "read file.txt: unexpected EOF" errors.Is(mye, io.EOF) # gives False errors.Is(mye. io.ErrUnexpectedEOF) # gives True
被包装的和包装的错误可以是任意 Python 类型,不一定是 error 或其子类。
error 还用于表示在 Python 级别由 Cython/nogil 调用返回的错误(见 Cython/nogil API)并保留 Cython/nogil 错误链以在 Python 级别进行检查。
Pygolang 错误链与 Python 错误链集成,并考虑了通过 raise X from Y(PEP 3134)创建的异常的 .__cause__ 属性。
字符串
b 和 u 提供了一种确保对象是字节或 unicode 的方法。使用 b(obj) 将 str/unicode/bytes 对象转换为 UTF-8 编码的字节串,而使用 u(obj) 将 str/unicode/bytes 对象转换为 unicode 字符串。例如
b("привет мир") # -> gives bytes corresponding to UTF-8 encoding of "привет мир". def f(s): s = u(s) # make sure s is unicode, decoding as UTF-8(*) if it was bytes. ... # (*) but see below about lack of decode errors.
编码和解码的转换永远不会失败,也不会丢失信息:b(u(·)) 和 u(b(·)) 总是对应于字节和 unicode 的恒等,即使字节输入不是有效的 UTF-8。
导入
gimport 提供了一种在 Go 工作空间中通过完整路径导入 Python 模块的方法。
例如
lonet = gimport('lab.nexedi.com/kirr/go123/xnet/lonet')
将导入以下之一
lab.nexedi.com/kirr/go123/xnet/lonet.py,或
lab.nexedi.com/kirr/go123/xnet/lonet/__init__.py
位于 src/ 下 $GOPATH 的位置。
Cython/nogil API
Cython 包 golang 提供了具有 goroutines、channels 等功能,与相应的 Python 包相对应的 nogil API。与 Python 版本相比,Cython API 不仅更快,而且由于 nogil 特性,允许在 Python 类似的语言中进行编程的同时构建无限制的并发系统。以下是对 Cython/nogil API 的简要描述
go 会启动一个新的任务 - 一个协程或线程,具体取决于激活的运行时。表示具有 Go 语义和类型 T 的元素的 chan[T]。使用 makechan[T] 创建新通道,并使用 chan[T].recv、chan[T].send、chan[T].close 进行通信。表示空通道的 nil。可以使用 select 在多个通道上进行多路复用。例如
cdef nogil: struct Point: int x int y void worker(chan[int] chi, chan[Point] chp): chi.send(1) cdef Point p p.x = 3 p.y = 4 chp.send(p) void myfunc(): cdef chan[int] chi = makechan[int]() # synchronous channel of integers cdef chan[Point] chp = makechan[Point](3) # channel with buffer of size 3 and Point elements go(worker, chi, chp) i = chi.recv() # will give 1 p = chp.recv() # will give Point(3,4) chp = nil # rebind chp to nil channel cdef cbool ok cdef int j = 33 _ = select([ chi.recvs(&i), # 0 chi.recvs(&i, &ok), # 1 chi.sends(&j), # 2 chp.recvs(&p), # 3 default, # 4 ]) if _ == 0: # i is what was received from chi ... if _ == 1: # (i, ok) is what was received from chi ... if _ == 2: # we know j was sent to chi ... if _ == 3: # this case will be never selected because # send/recv on nil channel block forever. ... if _ == 4: # default case ...
Python 通道由表示为 pychan cdef 类的 pychan 表示。携带非 Python 元素(pychan.dtype != DTYPE_PYOBJECT)的 Python 通道可以通过 pychan.chan_*() 转换为 Cython/nogil chan[T]。同样,Cython/nogil chan[T] 可以通过 pychan.from_chan_*() 包装到 pychan 中。这提供了在 nogil 和 Python 世界之间交互的机制。例如
def myfunc(pychan pych): if pych.dtype != DTYPE_INT: raise TypeError("expected chan[int]") cdef chan[int] ch = pych.chan_int() # pychan -> chan[int] with nogil: # use ch in nogil code. Both Python and nogil parts can # send/receive on the channel simultaneously. ... def mytick(): # -> pychan cdef chan[int] ch with nogil: # create a channel that is connected to some nogil task of the program ch = ... # wrap the channel into pychan. Both Python and nogil parts can # send/receive on the channel simultaneously. cdef pychan pych = pychan.from_chan_int(ch) # pychan <- chan[int] return pych
error 是表示错误的接口。使用 errors.New 和 fmt.errorf 可以从文本构建错误。错误可以通过实现 errorWrapper 接口并提供 .Unwrap() 方法来可选地包装另一个错误。使用 errors.Is 可以判断错误链中的某个项是否匹配目标。使用 fmt.errorf 与 %w 说明符提供了一种方便的方式来构建包装错误。例如
e1 = errors.New("problem") e2 = fmt.errorf("doing something for %s: %w", "joe", e1) e2.Error() # gives "doing something for joe: problem" errors.Is(e2, e1) # gives True
可以通过 pyerror cdef 类包装器实例化 pyerror.from_error() 将 error 暴露给 Python。通过 Python 级别的 error.Is,pyerror 保留了 Cython/nogil 错误链以进行检查。
panic 通过抛出 C 级别异常来停止当前 goroutine 的正常执行。在 Python/C 边界,必须使用 topyexc 将 C 级别异常转换为 Python 级别异常。例如
cdef void _do_something() nogil: ... panic("bug") # hit a bug # do_something is called by Python code - it is thus on Python/C boundary cdef void do_something() nogil except +topyexc: _do_something() def pydo_something(): with nogil: do_something()
请参阅 libgolang.h 和 golang.pxd 以了解 API 的详细信息。还可以参阅 testprog/golang_pyx_user/ 以了解使用 Pygolang 的 Cython/nogil 模式的示例项目。
额外的包和工具
还提供以下额外的包和工具来弥合 Python/Cython 和 Go 环境之间的差距
并发
除了 go 和通道之外,还提供了以下包来帮助以结构化方式处理并发:
golang.context (py, pyx) 提供上下文,用于在派生的 goroutines 之间传播截止时间、取消和任务范围值 [*]。
golang.sync (py, pyx) 提供了 sync.WorkGroup,用于派生一组在共同任务上工作的 goroutines。它还提供了底层原语 - 例如 sync.Once、sync.WaitGroup、sync.Mutex 和 sync.RWMutex - 有时也非常有用。
golang.time (py, pyx) 提供了与通道集成的计时器。
golang.os.signal (py, pyx) 通过通道提供信号处理。
字符串转换
qq(从 golang.gcompat 导入)提供了 %q 功能,该功能将像 Go 一样进行引用。例如,以下代码将打印出在 “ 中引用的名称,而不会转义可打印的 UTF-8 字符
print('hello %s' % qq(name))
qq接受 str 和 bytes(Python2 中的 unicode 和 str)以及任何可以转换为 str 的其他类型。
包 golang.strconv 提供了对转换例程的直接访问,例如 strconv.quote 和 strconv.unquote。
基准测试和测试
py.bench 允许以类似于 go test -bench 和 py.test 的方式对 Python 代码进行基准测试。例如,在以下代码上运行 py.bench
def bench_add(b): x, y = 1, 2 for i in xrange(b.N): x + y
会得到类似的结果
$ py.bench --count=3 x.py ... pymod: bench_add.py Benchmarkadd 50000000 0.020 µs/op Benchmarkadd 50000000 0.020 µs/op Benchmarkadd 50000000 0.020 µs/op
包 golang.testing 提供相应的运行时组件,例如 testing.B。
py.bench 以 Go 基准格式 生成输出,因此基准测试结果可以与标准 Go 工具进行分析和比较,例如使用 benchstat。此外,可以使用包 golang.x.perf.benchlib 在 Python 中加载和处理此类基准测试数据。
GPython选项
GPython 仿真并支持大多数 Python 命令行选项,例如 gpython -c <commands> 从命令行运行 Python 语句,或 gpython -m <module> 执行模块。这些选项的含义与标准 Python 相同,此处未进行说明。
以下列出了 GPython 特定的选项和环境变量
- -X gpython.runtime=(gevent|threads)
指定 GPython 应使用哪个运行时。 gevent 提供轻量级协程,而使用 threads 时,go 会生成完整的 OS 线程。gevent 是默认设置。也可以通过 $GPYTHON_RUNTIME 环境变量指定要使用的运行时。
Pygolang变更历史
0.1 (2022-01-26)
添加 os.signal 包,该包通过 nogil 通道提供信号处理。这允许构建不受 Python 标准模块 signal 限制的并发系统:如果主线程被阻塞或正忙于执行任何非 Python 工作,则信号传递不会延迟,可能无限期延迟(提交 1,2,3,示例用法)。
添加C++ API以支持IO。(提交1:3a838d24,提交2:3a131a51,提交3:c2471014,提交4:d358fa75,提交5:4690460b,提交6:2a35ef5b)。
修复使用gevent后端时未处理的panic导致的段错误崩溃(提交:07cae4e9,greenlet bug:greenlet bug)。
修复Python2中的print(qq(·))崩溃(提交:08dc5d10)。
0.0.9 (2021-12-08)
0.0.8 (2020-12-02)
添加对SlapOS的支持(提交1:60e89902,提交2:483df486,提交3:92bb5bcc,提交4:0fa9d6e7)。
添加在Nexedi测试基础设施下运行测试的方法(提交:d5b1eca0)。
修复通过相对路径调用(例如:./bin/gpython)时gpython崩溃的问题(提交:076cdd8f)。
更多针对gpython的修复以使其与CPython在命令行处理方面兼容(提交1:64088e8a,提交2:167912d3,提交3:26058b5b,提交4:21756bd3,提交5:11b367c6,提交6:8564dfdd,提交7:840a5eae,提交8:cd59f5a5)。
0.0.7 (2020-09-22)
添加以gevent或线程运行时运行gpython的方法。这允许在不强迫项目从线程切换到greenlets的情况下使用gpython(提交1:0e3da017,提交2:c0282565,提交3:a6b993c8)。
修复gpython以使其在命令行处理方面更兼容CPython(提交1:e6714e49,提交2:70c4c82f,提交3:b47edf42,提交4:a0016938,提交5:51925488,提交6:1f6f31cd,提交7:fb98e594)。
使recover在Python2上始终返回设置了.__traceback__的异常(提交:ccfc6db2)。
修复用于开发安装的pyx.build(提交:34b9c0cf)。
修复 macOS 上的 pyx.build (提交:fb662979)。
添加对 IPython 和 Pytest 集成补丁的测试(提交:0148cb89、2413b5ba、42ab98a6、09629367、6e31304d、b938af8b、a1ac2a45)。
修复即将到来的 Debian 11 上的 ThreadSanitizer/AddressSanitizer 支持(提交:49bb8dcd)。
0.0.6 (2020-02-28)
提供错误链支持。在并发系统中,操作栈通常与执行代码流不同,这使得代码栈跟踪对理解错误非常有用。错误链允许构建操作错误栈并检查结果错误(提交:fd95c88a、17798442、78d0c76f、337de0d7、03f88c0b、80ab5863、概述 1、概述 2)。
提供 unicode ↔ bytes 转换:函数 b(obj) 将 str/unicode/bytes 对象转换为 UTF-8 编码的字节串,而函数 u(obj) 将 str/unicode/bytes 对象转换为 Unicode 字符串。在编码和解码过程中,转换永远不会失败,也不会丢失信息:对于字节和 Unicode,b(u(·)) 和 u(b(·)) 始终是恒等函数,即使字节输入不是有效的 UTF-8。 (提交:bcb95cd5、073d81a8、5cc679ac、0561926a、8c459a99、3073ac98、e028cf28)。
将 nil 作为 nullptr 和 NULL 的别名(提交:60f6db6f、fc1c3e24、01ade7ac、230c81c4)。
添加包含 io.EOF 和 io.ErrUnexpectedEOF 的 io 包(提交:36ab859c)。
将 cxx.dict API 修正为遵循 libgolang 的逗号-OK 风格(提交:58fcdd87)。
为使用/链接到 libgolang 的项目提供 pyx.build.DSO (提交:64765688、cd67996e)。
修复 pyx.build.build_ext 以允许自定义(提交:8af78fc5)。
0.0.5 (2019-11-27)
支持类型化的Python通道。例如,chan(dtype='C.int')创建的通道元素类型是C int 而不是Python对象。除了提供运行时类型安全外,这还允许在Python和nogil世界之间构建交互(参见提交1,2,3,4,5,6,7,8,9,10,11)。
为C++/Cython/nogil类提供自动内存管理。所采用的方法补充了《Cython中的自动多线程安全内存管理类》(Gwenaël Samain等,2019年,博客文章)(参见提交1,2,3,4,5,6,7)。
作为Python和Cython/nogil API的一部分提供 sync.Mutex 和 sync.Sema(参见提交1,2,3,4,5,6)。
为 time 包提供C++/Cython/nogil API。Python级别的 time 成为一个小的Cython/nogil包装器(参见提交1,2,3,4,5,6,7,8,9,10,11,12,13,14)。
为 context 包提供C++/Cython/nogil API。Python级别的 context 成为一个小的Cython/nogil包装器(参见提交1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)。
提供C++/Cython/nogil API以支持sync包。Python级别的sync变成了Cython/nogil包的一个小型包装器(参见提交1,2,3,4,5,6,7,8,9)。
添加了包含errors.New函数的errors包,用于创建带有提供文本的新错误(参见提交)。
添加了包含fmt.sprintf和fmt.errorf函数的fmt包,用于将文本格式化为字符串和错误(参见提交)。
添加了包含strings.has_prefix,strings.split等工具的strings包(参见提交)。
添加了包含cxx.dict和cxx.set的cxx包,提供对STL哈希表和集合的直观接口(参见提交)。
教授如何使用defer来串联异常(PEP 3134),并调整跟踪回溯以包括异常原因/上下文,即使在Python2中也是如此(参见提交1,2,3,4,5)。
将build_ext作为pyx.build包API的一部分提供。这允许项目自定义其基于Pygolang的扩展的构建方式(参见提交1,2)。
修复了在错误路径上不会泄漏对象引用的select(参见提交)。
0.0.4 (2019-09-17)
0.0.3 (2019-08-29)
提供带有goroutines和channels的Cython/nogil API。与Python版本相比,Cython API不仅速度更快,而且由于nogil属性,可以在不受到Python全局解释器锁(GIL)限制的情况下构建并发系统。这项工作是由wendelin.core v2激发的,由于其设计,如果在pinner线程中尝试获取GIL,则会导致死锁。Python级别的goroutines和channels的实现变成了围绕Cython/nogil API的微小包装。这使Python级别的golang包的速度提高了约5倍(参见提交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)。
提供一种安装Pygolang的方法,通过形式为pygolang[<package>]的额外需求。例如,pygolang[x.perf.benchlib]会额外选择NumPy,pygolang[pyx.build]则需要构建系统所需的所有内容,而pygolang[all]则会选择所有内容(参见提交)。
修复了sync.WorkGroup测试中的死锁(参见提交)。
修复了@func(cls) def name,以防止在调用上下文中覆盖name(参见提交)。
修复了sync.WorkGroup,使其传播所有异常类型,而不仅仅是派生自Exception的类型(参见提交)。
在sync.WorkGroup实现中将threading.Event替换为chan。这消除了对外部信号量+等待列表代码的依赖,并加快了sync.WorkGroup的运行速度(参见提交)。
通过不在运行时使用@func来加速sync.WorkGroup(参见提交)。
为chan、select、@func和defer添加基准测试(参见提交)。
本版本发布是为了纪念 Вера Павловна Супрун。
0.0.2 (2019-05-16)
添加 time 包,包含 time.Timer 和 time.Ticker(提交 1,提交 2,提交 3)。
为 context 包添加对截止时间和超时的支持(提交 1,提交 2,提交 3,提交 4)。
0.0.1 (2019-05-09)
支持 nil 通道(提交)。
将 context 包添加到传播取消和子任务范围内值的功能中,以便在启动的 goroutine 之间进行(提交,概述)。
添加 sync 包,包含 sync.WorkGroup,以启动处理公共任务的 goroutine 组(提交 1,提交 2)。
删除过时的 @method(提交)。
0.0.0.dev8(2019-03-24)
修复 gpython 以正确初始化 sys.path(提交)。
修复通道测试,使其在周围操作系统负载的情况下都能通过(提交)。
弃用 @method(cls),改用 @func(cls)(提交)。
支持 PyPy2 和 PyPy3(提交 1,提交 2,提交 3)。
0.0.0.dev7(2019-01-16)
提供 gpython 解释器,默认编码为 UTF-8,集成 gevent,并将 go、chan、select 等放入内置命名空间(提交)。
0.0.0.dev6(2018-12-13)
添加 strconv 包,包含 quote 和 unquote(提交 1,提交 2)。
也支持 PyPy(提交)。
0.0.0.dev5(2018-10-30)
修复导致多个情况可能同时执行的 select 错误(提交 1,提交 2,提交 3)。
添加 defer 和 recover(提交)。实现部分受 Denis Kolodin 的工作启发(1,2)。
修复 Python3 上的 @method(提交)。
泄漏的 goroutine 不会再阻止整个程序退出(提交 1,提交 2)。
0.0.0.dev4(2018-07-04)
添加 py.bench 程序和 golang.testing 包,以及相应的组件(提交)。
0.0.0.dev3(2018-07-02)
同时支持 Python2 和 Python3;现在 qq 不再对可打印的 UTF-8 字符进行转义(提交 1,提交 2,提交 3)。
golang/x/perf/benchlib: 新模块,用于加载和处理 Go 基准格式数据(提交)。
0.0.0.dev2(2018-06-20)
转换为完整的 pygolang: 除了 gimport 外,还提供了 go、chan、select、method 和 gcompat.qq,并在提交中实现。虽然实现速度不是很快,但应该能够正确工作,包括 select - select 向同步通道发送。
0.0.0.dev1 (2018-05-21)
初始版本;仅提供 gimport 功能 (提交).
项目详情
pygolang-0.1.tar.gz 的哈希
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 2f69185189a6fbe3b47b4cc5ec7540ca783e1e85d25df1f0bd4b7581560e27da |
|
MD5 | 9ec51541a7bdfa57d0cfc0e11248f061 |
|
BLAKE2b-256 | 497874e8770668c24c51d93bcfdbb4681e545fd48e2b446cbb084b964bf95bf7 |