More Than Just Bindings

PyZMQ is ostensibly the Python bindings for ØMQ, but the project, following Python’s ‘batteries included’ philosophy, provides more than just Python methods and objects for calling into the ØMQ C++ library.

The Core as Bindings

PyZMQ is currently broken up into four subpackages. First, is the Core. zmq.core contains the actual bindings for ZeroMQ, and no extended functionality beyond the very basic. The core modules are split, such that each basic ZeroMQ object (or function, if no object is associated) is a separate module, e.g. zmq.core.context contains the Context object, zmq.core.poll contains a Poller object, as well as the select() function, etc. ZMQ constants are, for convenience, all kept together in zmq.core.constants.

There are two reasons for breaking the core into submodules: recompilation and derivative projects. The monolithic PyZMQ became quite tedious to have to recompile everything for a small change to a single object. With separate files, that’s no longer necessary. The second reason has to do with Cython. PyZMQ is written in Cython, a tool for efficiently writing C-extensions for Python. By separating out our objects into individual pyx files, each with their declarations in a pxd header, other projects can write extensions in Cython and call directly to ZeroMQ at the C-level without the penalty of going through our Python objects.

Thread Safety

In ØMQ, Contexts are threadsafe objects, but Sockets are not. It is safe to use a single Context (e.g. via zmq.Context.instance()) in your entire multithreaded application, but you should create sockets on a per-thread basis. If you share sockets across threads, you are likely to encounter uncatchable c-level crashes of your application unless you use judicious application of threading.Lock, but this approach is not recommended.

See also

ZeroMQ API note on threadsafety on 2.2 or 3.2

Socket Options as Attributes

New in version 2.1.9.

In 0MQ, socket options are set/retrieved with the set/getsockopt() methods. With the class-based approach in pyzmq, it would be logical to perform these operations with simple attribute access, and this has been added in pyzmq 2.1.9. Simply assign to or request a Socket attribute with the (case-insensitive) name of a sockopt, and it should behave just as you would expect:

s = ctx.socket(zmq.DEALER)
s.identity = b'dealer'
s.hwm = 10
s.events
# 0
s.fd
# 16

Default Options on the Context

New in version 2.1.11.

Just like setting socket options as attributes on Sockets, you can do the same on Contexts. This affects the default options of any new sockets created after the assignment.

ctx = zmq.Context()
ctx.linger = 0
rep = ctx.socket(zmq.REP)
req = ctx.socket(zmq.REQ)

Socket options that do not apply to a socket (e.g. SUBSCRIBE on non-SUB sockets) will simply be ignored.

Core Extensions

We have extended the core functionality in two ways that appear inside the core bindings, and are not general ØMQ features.

Builtin Serialization

First, we added common serialization with the builtin json and pickle as first-class methods to the Socket class. A socket has the methods send_json() and send_pyobj(), which correspond to sending an object over the wire after serializing with json and pickle respectively, and any object sent via those methods can be reconstructed with the recv_json() and recv_pyobj() methods. Unicode strings are other objects that are not unambiguously sendable over the wire, so we include send_string() and recv_string() that simply send bytes after encoding the message (‘utf-8’ is the default).

See also

MessageTracker

The second extension of basic ØMQ functionality is the MessageTracker. The MessageTracker is an object used to track when the underlying ZeroMQ is done with a message buffer. One of the main use cases for ØMQ in Python is the ability to perform non-copying sends. Thanks to Python’s buffer interface, many objects (including NumPy arrays) provide the buffer interface, and are thus directly sendable. However, as with any asynchronous non-copying messaging system like ØMQ or MPI, it can be important to know when the message has actually been sent, so it is safe again to edit the buffer without worry of corrupting the message. This is what the MessageTracker is for.

The MessageTracker is a simple object, but there is a penalty to its use. Since by its very nature, the MessageTracker must involve threadsafe communication (specifically a builtin Queue object), instantiating a MessageTracker takes a modest amount of time (10s of µs), so in situations instantiating many small messages, this can actually dominate performance. As a result, tracking is optional, via the track flag, which is optionally passed, always defaulting to False, in each of the three places where a Frame object (the pyzmq object for wrapping a segment of a message) is instantiated: The Frame constructor, and non-copying sends and receives.

A MessageTracker is very simple, and has just one method and one attribute. The property MessageTracker.done will be True when the Frame(s) being tracked are no longer in use by ØMQ, and MessageTracker.wait() will block, waiting for the Frame(s) to be released.

Note

A Frame cannot be tracked after it has been instantiated without tracking. If a Frame is to even have the option of tracking, it must be constructed with track=True.

Extensions

So far, PyZMQ includes four extensions to core ØMQ that we found basic enough to be included in PyZMQ itself:

  • zmq.log : Logging handlers for hooking Python logging up to the network
  • zmq.devices : Custom devices and objects for running devices in the background
  • zmq.eventloop : The Tornado event loop, adapted for use with ØMQ sockets.
  • zmq.ssh : Simple tools for tunneling zeromq connections via ssh.