跳转到主要内容

Python和Cython的Go-like功能

项目描述

golang 为Python提供类似Go的功能

  • gpython 是一个支持轻量级线程的Python解释器。

  • go 创建轻量级线程。

  • chanselect 提供具有Go语义的通道。

  • func 允许在类外定义方法。

  • defer 允许在主控制流程中安排清理。

  • error 和包 errors 提供错误链。

  • bu 提供确保对象是字节或Unicode的方法。

  • gimport 允许在Go工作空间中通过完整路径导入Python模块。

golang.pyx 提供类似功能用于Cython/nogil。

还提供了额外的包和工具来填补Python/Cython和Go环境之间的其他差距。(点击查看)

GPython

命令 gpython 提供了一个支持通过紧集成 gevent 实现轻量级线程的Python解释器。GPython的标准库与Python标准库的API兼容,但提供轻量级协程代替OS线程,并通过基于 libuv/libev 的IO调度器内部组织IO。因此,程序可以以低廉的成本创建大量的协程,并且像 timesocketsslsubprocess 等模块都可以被所有协程同时使用,就像每个协程都是一个完整的OS线程一样。这为在不更改并发模型和现有代码的情况下扩展程序提供了能力。

此外,GPython始终将UTF-8设置为默认编码,并将 gochanselect 等放入内置命名空间。

协程和通道

go生成一个协程,如果没有激活gevent,则生成线程。可以通过通道在线程或协程之间交换数据。使用 chan.recvchan.sendchan.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上也是如此。

为了完整性,还有 recoverpanic,允许以Go风格的错误处理编程,例如

def _():
   r = recover()
   if r is not None:
      print("recovered. error was: %s" % (r,))
defer(_)

...

panic("aaa")

但是,recoverpanic 可能不太有用,因为它们可以用 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 YPEP 3134)创建的异常的 .__cause__ 属性。

字符串

bu 提供了一种确保对象是字节或 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].recvchan[T].sendchan[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.Newfmt.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.Ispyerror 保留了 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.hgolang.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.Oncesync.WaitGroupsync.Mutexsync.RWMutex - 有时也非常有用。

  • golang.time (py, pyx) 提供了与通道集成的计时器。

  • golang.os.signal (py, pyx) 通过通道提供信号处理。

字符串转换

qq(从 golang.gcompat 导入)提供了 %q 功能,该功能将像 Go 一样进行引用。例如,以下代码将打印出在 中引用的名称,而不会转义可打印的 UTF-8 字符

print('hello %s' % qq(name))

qq接受 strbytes(Python2 中的 unicodestr)以及任何可以转换为 str 的其他类型。

golang.strconv 提供了对转换例程的直接访问,例如 strconv.quotestrconv.unquote

基准测试和测试

py.bench 允许以类似于 go test -benchpy.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.benchGo 基准格式 生成输出,因此基准测试结果可以与标准 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 工作,则信号传递不会延迟,可能无限期延迟(提交 123示例用法)。

  • 添加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)

  • 修复从已取消的父级创建新上下文时的死锁问题(提交1:d0688e21,提交2:58d4cbfe)。

  • sync.WorkGroup中添加对“with”语句的支持。这在某些情况下非常有用,在Python世界中被称为“结构化并发”(提交:6eb80104,讨论:讨论)。

  • 修复strconv.unqoute以处理Go strconv.Qoute可能产生的所有输入(提交:78b4b41c)。

  • 更多针对gpython的修复以使其与CPython在处理stdin上的程序、交互式会话和__main__模块设置方面兼容(提交1:6cc4bf32,提交2:22fb559a,提交3:95c7cce9,提交4:2351dd27,提交5:e205dbf6)。

0.0.8 (2020-12-02)

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)。

  • 使qq可用于带字节和字符串格式的任何类型的参数(提交1:85a1765d,提交2:edc7aaab)。

  • 使recover在Python2上始终返回设置了.__traceback__的异常(提交:ccfc6db2)。

  • 修复用于开发安装的pyx.build(提交:34b9c0cf)。

  • 修复 macOS 上的 pyx.build (提交:fb662979)。

  • 添加对 IPython 和 Pytest 集成补丁的测试(提交:0148cb892413b5ba42ab98a6096293676e31304db938af8ba1ac2a45)。

  • 添加对 Python38 的支持(提交:792cbd6c1f184095)。

  • 修复即将到来的 Debian 11 上的 ThreadSanitizer/AddressSanitizer 支持(提交:49bb8dcd)。

                本版本中,Pygolang 已被纳入 Nexedi 软件堆栈

0.0.6 (2020-02-28)

  • 提供错误链支持。在并发系统中,操作栈通常与执行代码流不同,这使得代码栈跟踪对理解错误非常有用。错误链允许构建操作错误栈并检查结果错误(提交:fd95c88a1779844278d0c76f337de0d703f88c0b80ab5863概述 1概述 2)。

  • 提供 unicodebytes 转换:函数 b(obj) 将 str/unicode/bytes 对象转换为 UTF-8 编码的字节串,而函数 u(obj) 将 str/unicode/bytes 对象转换为 Unicode 字符串。在编码和解码过程中,转换永远不会失败,也不会丢失信息:对于字节和 Unicode,b(u(·))u(b(·)) 始终是恒等函数,即使字节输入不是有效的 UTF-8。 (提交:bcb95cd5073d81a85cc679ac0561926a8c459a993073ac98e028cf28)。

  • 提供 sync.RWMutex (提交:1ad3c2d5a9345a98)。

  • nil 作为 nullptr 和 NULL 的别名(提交:60f6db6ffc1c3e2401ade7ac230c81c4)。

  • 添加包含 io.EOFio.ErrUnexpectedEOFio 包(提交:36ab859c)。

  • cxx.dict API 修正为遵循 libgolang 的逗号-OK 风格(提交:58fcdd87)。

  • 为使用/链接到 libgolang 的项目提供 pyx.build.DSO (提交:64765688cd67996e)。

  • 修复 pyx.build.build_ext 以允许自定义(提交:8af78fc5)。

                本版本由 wendelin.core 版本 2 驱动。

0.0.5 (2019-11-27)

  • 支持类型化的Python通道。例如,chan(dtype='C.int')创建的通道元素类型是C int 而不是Python对象。除了提供运行时类型安全外,这还允许在Python和nogil世界之间构建交互(参见提交1234567891011)。

  • 为C++/Cython/nogil类提供自动内存管理。所采用的方法补充了《Cython中的自动多线程安全内存管理类》(Gwenaël Samain等,2019年,博客文章)(参见提交1234567)。

  • 为基库提供的空和 error 接口提供最小支持(参见提交12)。

  • 作为Python和Cython/nogil API的一部分提供 sync.Mutexsync.Sema(参见提交123456)。

  • time 包提供C++/Cython/nogil API。Python级别的 time 成为一个小的Cython/nogil包装器(参见提交1234567891011121314)。

  • context 包提供C++/Cython/nogil API。Python级别的 context 成为一个小的Cython/nogil包装器(参见提交123456789101112131415)。

  • 提供C++/Cython/nogil API以支持sync包。Python级别的sync变成了Cython/nogil包的一个小型包装器(参见提交123456789)。

  • 添加了包含errors.New函数的errors包,用于创建带有提供文本的新错误(参见提交)。

  • 添加了包含fmt.sprintffmt.errorf函数的fmt包,用于将文本格式化为字符串和错误(参见提交)。

  • 添加了包含strings.has_prefixstrings.split等工具的strings包(参见提交)。

  • 添加了包含cxx.dictcxx.setcxx包,提供对STL哈希表和集合的直观接口(参见提交)。

  • 教授如何使用defer来串联异常(PEP 3134),并调整跟踪回溯以包括异常原因/上下文,即使在Python2中也是如此(参见提交12345)。

  • 也将defer作为C++ API的一部分提供(参见提交123)。

  • build_ext作为pyx.build包API的一部分提供。这允许项目自定义其基于Pygolang的扩展的构建方式(参见提交12)。

  • 修复了recover以清除当前异常(参见提交12)。

  • 修复了在错误路径上不会泄漏对象引用的select(参见提交)。

  • 修复了gevent运行时,在运行时调用期间保留Python异常状态(参见提交12)。

                本版本由 wendelin.core 版本 2 驱动。
                本发行版献给 Бася 的纪念。

0.0.4 (2019-09-17)

  • 将ThreadSanitizer,AddressSanitizer和Python调试构建添加到测试覆盖率中(参见提交)。

  • 修复了closerecvselect中的竞争条件错误(参见提交123456)。在进行并发质量保证时还发现了一个25年的Python竞争条件(参见提交7Python bugPyPy bug)。

  • 如果C级别的恐慌导致终止,则现在会打印其参数(参见提交)。

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倍(参见提交123456789101112131415161718192021222324252627)。

  • 提供一种安装Pygolang的方法,通过形式为pygolang[<package>]的额外需求。例如,pygolang[x.perf.benchlib]会额外选择NumPy,pygolang[pyx.build]则需要构建系统所需的所有内容,而pygolang[all]则会选择所有内容(参见提交)。

  • 改进测试,在许多地方更彻底地测试实现(参见提交123456)。

  • 修复了缓冲通道发送和接收中的竞态条件错误(参见提交12)。

  • 修复了sync.WorkGroup测试中的死锁(参见提交)。

  • 修复了@func(cls) def name,以防止在调用上下文中覆盖name(参见提交)。

  • 修复了sync.WorkGroup,使其传播所有异常类型,而不仅仅是派生自Exception的类型(参见提交)。

  • sync.WorkGroup实现中将threading.Event替换为chan。这消除了对外部信号量+等待列表代码的依赖,并加快了sync.WorkGroup的运行速度(参见提交)。

  • 通过不在运行时使用@func来加速sync.WorkGroup(参见提交)。

  • chanselect@funcdefer添加基准测试(参见提交)。

                本版本发布是为了纪念 Вера Павловна Супрун

0.0.2 (2019-05-16)

  • 添加 time 包,包含 time.Timertime.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)(提交)。

  • 支持 PyPy2PyPy3(提交 1,提交 2,提交 3)。

0.0.0.dev7(2019-01-16)

  • 提供 gpython 解释器,默认编码为 UTF-8,集成 gevent,并将 gochanselect 等放入内置命名空间(提交)。

0.0.0.dev6(2018-12-13)

  • 添加 strconv 包,包含 quoteunquote(提交 1,提交 2)。

  • 也支持 PyPy(提交)。

0.0.0.dev5(2018-10-30)

  • 修复导致多个情况可能同时执行的 select 错误(提交 1,提交 2,提交 3)。

  • 添加 deferrecover(提交)。实现部分受 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 外,还提供了 gochanselectmethodgcompat.qq,并在提交中实现。虽然实现速度不是很快,但应该能够正确工作,包括 select - select 向同步通道发送。

0.0.0.dev1 (2018-05-21)

  • 初始版本;仅提供 gimport 功能 (提交).

项目详情


下载文件

下载适合您平台的文件。如果您不确定要选择哪个,请了解有关 安装包 的更多信息。

源代码分发

pygolang-0.1.tar.gz (222.0 kB 查看哈希)

上传时间

由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面