Skip to main content

Go-like features for Python and Cython

Project description

Package golang provides Go-like features for Python:

  • gpython is Python interpreter with support for lightweight threads.

  • go spawns lightweight thread.

  • chan and select provide channels with Go semantic.

  • func allows to define methods separate from class.

  • defer allows to schedule a cleanup from the main control flow.

  • gimport allows to import python modules by full path in a Go workspace.

Package golang.pyx provides similar features for Cython/nogil.

Additional packages and utilities are also provided to close other gaps between Python/Cython and Go environments.

GPython

Command gpython provides Python interpreter that supports lightweight threads via tight integration with gevent. The standard library of GPython is API compatible with Python standard library, but inplace of OS threads lightweight coroutines are provided, and IO is internally organized via libuv/libev-based IO scheduler. Consequently programs can spawn lots of coroutines cheaply, and modules like time, socket, ssl, subprocess etc - all could be used from all coroutines simultaneously, and in the same blocking way as if every coroutine was a full OS thread. This gives ability to scale programs without changing concurrency model and existing code.

Additionally GPython sets UTF-8 to be default encoding always, and puts go, chan, select etc into builtin namespace.

Goroutines and channels

go spawns a coroutine, or thread if gevent was not activated. It is possible to exchange data in between either threads or coroutines via channels. chan creates a new channel with Go semantic - either synchronous or buffered. Use chan.recv, chan.send and chan.close for communication. nilchan stands for the nil channel. select can be used to multiplex on several channels. For example:

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
    ...

Methods

func decorator allows to define methods separate from class.

For example:

@func(MyClass)
def my_method(self, ...):
    ...

will define MyClass.my_method().

func can be also used on just functions, for example:

@func
def my_function(...):
    ...

Defer / recover / panic

defer allows to schedule a cleanup to be executed when current function returns. It is similar to try/finally but does not force the cleanup part to be far away in the end. For example:

wc = wcfs.join(zurl)    │     wc = wcfs.join(zurl)
defer(wc.close)         │     try:
                        │        ...
...                     │        ...
...                     │        ...
...                     │     finally:
                        │        wc.close()

For completeness there is recover and panic that allow to program with Go-style error handling, for example:

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

...

panic("aaa")

But recover and panic are probably of less utility since they can be practically natively modelled with try/except.

If defer is used, the function that uses it must be wrapped with @func decorator.

Import

gimport provides way to import python modules by full path in a Go workspace.

For example

lonet = gimport('lab.nexedi.com/kirr/go123/xnet/lonet')

will import either

  • lab.nexedi.com/kirr/go123/xnet/lonet.py, or

  • lab.nexedi.com/kirr/go123/xnet/lonet/__init__.py

located in src/ under $GOPATH.

Cython/nogil API

Cython package golang provides nogil API with goroutines, channels and other features that mirror corresponding Python package. Cython API is not only faster compared to Python version, but also, due to nogil property, allows to build concurrent systems without limitations imposed by Python’s GIL. All that while still programming in Python-like language. Brief description of Cython/nogil API follows:

go spawns new task - a coroutine, or thread, depending on activated runtime. chan[T] represents a channel with Go semantic and elements of type T. Use makechan[T] to create new channel, and chan[T].recv, chan[T].send, chan[T].close for communication. nil stands for the nil channel. select can be used to multiplex on several channels. For example:

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
          ...

panic stops normal execution of current goroutine by throwing a C-level exception. On Python/C boundaries C-level exceptions have to be converted to Python-level exceptions with topyexc. For example:

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

See libgolang.h and golang.pxd for details of the API. See also testprog/golang_pyx_user/ for demo project that uses Pygolang in Cython/nogil mode.


Additional packages and utilities

The following additional packages and utilities are also provided to close gaps between Python/Cython and Go environments:

Concurrency

In addition to go and channels, the following packages are provided to help handle concurrency in structured ways:

  • golang.context provides contexts to propagate deadlines, cancellation and task-scoped values among spawned goroutines [*].

  • golang.sync provides sync.WorkGroup to spawn group of goroutines working on a common task. It also provides low-level primitives - for example sync.Once and sync.WaitGroup - that are sometimes useful too.

  • golang.time provides timers integrated with channels.

String conversion

qq (import from golang.gcompat) provides %q functionality that quotes as Go would do. For example the following code will print name quoted in without escaping printable UTF-8 characters:

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

qq accepts both str and bytes (unicode and str on Python2) and also any other type that can be converted to str.

Package golang.strconv provides direct access to conversion routines, for example strconv.quote and strconv.unquote.

Benchmarking and testing

py.bench allows to benchmark python code similarly to go test -bench and py.test. For example, running py.bench on the following code:

def bench_add(b):
    x, y = 1, 2
    for i in xrange(b.N):
        x + y

gives something like:

$ 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

Package golang.testing provides corresponding runtime bits, e.g. testing.B.

py.bench produces output in Go benchmark format, and so benchmark results can be analyzed and compared with standard Go tools, for example with benchstat. Additionally package golang.x.perf.benchlib can be used to load and process such benchmarking data in Python.


Pygolang change history

0.0.4 (2019-09-17)

  • Add ThreadSanitizer, AddressSanitizer and Python debug builds to testing coverage (commit).

  • Fix race bugs in close, recv and select (commit 1, 2, 3, 4, 5, 6). A 25-years old race condition in Python was also discovered while doing quality assurance on concurrency (commit 7, Python bug, PyPy bug).

  • If C-level panic causes termination, its argument is now printed (commit).

0.0.3 (2019-08-29)

  • Provide Cython/nogil API with goroutines and channels. Cython API is not only faster compared to Python version, but also, due to nogil property, allows to build concurrent systems without limitations imposed by Python’s GIL. This work was motivated by wendelin.core v2, which, due to its design, would deadlock if it tries to take the GIL in its pinner thread. Implementation of Python-level goroutines and channels becomes tiny wrapper around Cython/nogil API. This brings in ~5x speedup to Python-level golang package along the way (commit 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).

  • Provide way to install Pygolang with extra requirements in the form of pygolang[<package>]. For example pygolang[x.perf.benchlib] additionally selects NumPy, pygolang[pyx.build] - everything needed by build system, and pygolang[all] selects everything (commit).

  • Improve tests to exercise the implementation more thoroughly in many places (commit 1, 2, 3, 4, 5, 6).

  • Fix race bugs in buffered channel send and receive (commit 1, 2).

  • Fix deadlock in sync.WorkGroup tests (commit).

  • Fix @func(cls) def name not to override name in calling context (commit).

  • Fix sync.WorkGroup to propagate all exception types, not only those derived from Exception (commit).

  • Replace threading.Event with chan in sync.WorkGroup implementation. This removes reliance on outside semaphore+waitlist code and speeds up sync.WorkGroup along the way (commit).

  • Speedup sync.WorkGroup by not using @func at runtime (commit).

  • Add benchmarks for chan, select, @func and defer (commit).

                This release is dedicated to the memory of Вера Павловна Супрун.

0.0.2 (2019-05-16)

  • Add time package with time.Timer and time.Ticker (commit 1, 2, 3).

  • Add support for deadlines and timeouts to context package (commit 1, 2, 3, 4).

0.0.1 (2019-05-09)

  • Add support for nil channels (commit).

  • Add context package to propagate cancellation and task-scoped values among spawned goroutines (commit, overview).

  • Add sync package with sync.WorkGroup to spawn group of goroutines working on a common task (commit 1, 2).

  • Remove deprecated @method (commit).

0.0.0.dev8 (2019-03-24)

  • Fix gpython to properly initialize sys.path (commit).

  • Fix channel tests to pass irregardless of surround OS load (commit).

  • Deprecate @method(cls) in favour of @func(cls) (commit).

  • Support both PyPy2 and PyPy3 (commit 1, 2, 3).

0.0.0.dev7 (2019-01-16)

  • Provide gpython interpreter, that sets UTF-8 as default encoding, integrates gevent and puts go, chan, select etc into builtin namespace (commit).

0.0.0.dev6 (2018-12-13)

  • Add strconv package with quote and unquote (commit 1, 2).

  • Support PyPy as well (commit).

0.0.0.dev5 (2018-10-30)

  • Fix select bug that was causing several cases to be potentially executed at the same time (commit 1, 2, 3).

  • Add defer and recover (commit). The implementation is partly inspired by work of Denis Kolodin (1, 2).

  • Fix @method on Python3 (commit).

  • A leaked goroutine no longer prevents whole program to exit (commit 1, 2).

0.0.0.dev4 (2018-07-04)

  • Add py.bench program and golang.testing package with corresponding bits (commit).

0.0.0.dev3 (2018-07-02)

  • Support both Python2 and Python3; qq now does not escape printable UTF-8 characters. (commit 1, 2, 3).

  • golang/x/perf/benchlib: New module to load & work with data in Go benchmark format (commit).

0.0.0.dev2 (2018-06-20)

  • Turn into full pygolang: go, chan, select, method and gcompat.qq are provided in addition to gimport (commit). The implementation is not very fast, but should be working correctly including select - select sends for synchronous channels.

0.0.0.dev1 (2018-05-21)

  • Initial release; gimport functionality only (commit).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pygolang-0.0.4.tar.gz (103.1 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page