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.
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
zmq.core.poll contains a
Poller object, as well
select() function, etc. ZMQ constants are, for convenience, all kept
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.
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.
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
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.
libzmq constants as Enums#
New in version 23.
libzmq constants are now available as Python enums, making it easier to enumerate socket options, etc.
New in version 14: Context/Sockets as context managers
New in version 20: bind/connect context managers
For more Pythonic resource management, contexts and sockets can be used as context managers. Just like standard-library socket and file methods, entering a context:
with zmq.Context() as ctx:
with ctx.socket(zmq.PUSH) as s:
# exiting Socket context closes socket
# exiting Context context terminates context
In addition, each bind/connect call may be used as a context:
# exiting connect context calls socket.disconnect(url)
We have extended the core functionality in two ways that appear inside the
bindings, and are not general ØMQ features.
First, we added common serialization with the builtin
as first-class methods to the
Socket class. A socket has the methods
send_pyobj(), which correspond to sending an
object over the wire after serializing with
and any object sent via those methods can be reconstructed with the
recv_pyobj() methods. Unicode strings are
other objects that are not unambiguously sendable over the wire, so we include
recv_string() that simply send bytes
after encoding the message (‘utf-8’ is the default).
The second extension of basic ØMQ functionality is 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
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
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
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.
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
So far, PyZMQ includes four extensions to core ØMQ that we found basic enough to be included in PyZMQ itself: