mmfutils.contexts

Various useful contexts.

class mmfutils.contexts.CoroutineWrapper(coroutine)[source]

Bases: object

Wrapper for coroutine contexts that allows them to function as a context but also as a function. Similar to open() which may be used both in a function or as a file object. Note: be sure to call close() if you do not use this as a context.

close()[source]
send(*v)[source]
class mmfutils.contexts.NoInterrupt(ignore=True)[source]

Bases: object

Suspend the various signals during the execution block and a simple mechanism to allow threads to be interrupted.

Parameters

ignore (bool) – If True, then do not raise a KeyboardInterrupt if a soft interrupt is caught unless forced by multiple interrupt requests in a limited time.

There are two main entry points: globally by calling the suspend() method, and within a NoInterrupt() context.

Main Thread

When executed in a context from the main thread, a signal handler is established which captures interrupt signals and represents them instead as a boolean flag (conventionally called “interrupted”).

Global interrupt suppression can be enabled by creating a NoInterrupt() instance and calling suspend(). This will stay in effect until restore() is called, a forcing interrupt is received, or the instance is deleted. Additional calls to suspend() will reinstall the handlers, but they will not be nested.

Interrupts can also be suspended in contexts. These can be nested. These instances will become False at the end of the context.

Auxiliary Threads

Auxiliary threads can create instances of NoInterrupt() or use contexts, but cannot call suspend() or restore(). In these cases the context does not suspend signals (see below), but the flag is still useful as it can act as a signal force the auxiliary thread to terminate if an interrupt is received in the main thread.

A couple of notes about using the context in auxiliary threads.

  1. Either suspend() must be called globally or a context must first be created in the main thread - otherwise the signal handlers will not be installed. An exception will be raised if an auxiliary thread tries to create a context without the handlers being installed. this case.

  2. As stated in the python documents, signal handlers are always executed in the main thread. Likewise, only the main thread is allowed to set new signal handlers. Thus, the signal interrupting facilities provided here only work properly in the main thread. Also, forcing an interrupt cannot raise an exception in the auxiliary threads: one must wait for them to respond to the changed “interrupted” value.

    For more information about killing threads see:

force_n

Number of interrupts to force signal.

Type

int

force_timeout

Time in which force_n interrupts must be received to trigger a forced interrupt.

Type

float

Examples

The simplest use-cases look like these:

Simple context:

>>> with NoInterrupt():
...    pass             # do something

Context with a cleanly aborted loop:

>>> with NoInterrupt() as interrupted:
...     done = False
...     while not interrupted and not done:
...         # Do something
...         done = True

Map:

>>> NoInterrupt().map(abs, [1, -1, 2, -2])
[1, 1, 2, 2]

Keyboard interrupt signals are suspended during the execution of the block unless forced by the user (3 rapid interrupts within 1s). Interrupts are ignored by default unless ignore=False is specified, in which case they will be raised when the context is ended.

If you want to control when you exit the block, use the interrupted flag. This could be used, for example, while plotting frames in an animation (see doc/Animation.ipynb). Without the NoInterrupt() context, if the user sends a keyboard interrupt to the process while plotting, at best, a huge stack-trace is produced, and at worst, the kernel will crash (randomly depending on where the interrupt was received). With this context, the interrupt will change interrupted to True so you can exit the context when it is safe.

The last case is mapping a function to data. This will allow the user to interrupt the process between function calls.

In the following examples we demonstrate this by simulating interrupts

>>> import os, signal, time
>>> def simulate_interrupt(force=False):
...     os.kill(os.getpid(), signal.SIGINT)
...     if force:
...         # Simulated a forced interrupt with multiple signals
...         os.kill(os.getpid(), signal.SIGINT)
...         os.kill(os.getpid(), signal.SIGINT)
...     time.sleep(0.1)   # Wait so signal can be received predictably

This loop will get interrupted in the middle so that m and n will not be the same.

>>> def f(n, interrupted=False, force=False, interrupt=True):
...     while n[0] < 10 and not interrupted:
...         n[0] += 1
...         if n[0] == 5 and interrupt:
...             simulate_interrupt(force=force)
...         n[1] += 1
>>> n = [0, 0]
>>> f(n, interrupt=False)
>>> n
[10, 10]
>>> n = [0, 0]
>>> try:  # All doctests need to be wrapped in try blocks to not kill py.test!
...     f(n)
... except KeyboardInterrupt as err:
...     print("KeyboardInterrupt: {}".format(err))
KeyboardInterrupt:
>>> n
[5, 4]

Now we protect the loop from interrupts. >>> n = [0, 0] >>> try: … with NoInterrupt(ignore=False) as interrupted: … f(n) … except KeyboardInterrupt as err: … print(“KeyboardInterrupt: {}”.format(err)) KeyboardInterrupt: >>> n [10, 10]

One can ignore the exception if desired (this is the default as of 0.4.11): >>> n = [0, 0] >>> with NoInterrupt() as interrupted: … f(n) >>> n [10, 10]

Three rapid exceptions will still force an interrupt when it occurs. This might occur at random places in your code, so don’t do this unless you really need to stop the process. >>> n = [0, 0] >>> try: … with NoInterrupt(ignore=False) as interrupted: … f(n, force=True) … except KeyboardInterrupt as err: … print(“KeyboardInterrupt: {}”.format(err)) KeyboardInterrupt: >>> n [5, 4]

If f() is slow, we might want to interrupt it at safe times. This is what the interrupted flag is for:

>>> n = [0, 0]
>>> try:
...     with NoInterrupt(ignore=False) as interrupted:
...         f(n, interrupted)
... except KeyboardInterrupt as err:
...     print("KeyboardInterrupt: {}".format(err))
KeyboardInterrupt:
>>> n
[5, 5]

Again: the exception can be ignored >>> n = [0, 0] >>> with NoInterrupt() as interrupted: … f(n, interrupted) >>> n [5, 5]

__bool__()[source]

Return True if interrupted.

__enter__()[source]

Enter context.

__nonzero__()

Return True if interrupted.

force_n = 3
force_timeout = 1
classmethod handle_original_signal(signum, frame)[source]

Call the original handler.

classmethod handle_signal(signum, frame)[source]

Custom signal handler.

This stores the signal for later processing unless it was forced or there are no current contexts, in which case the original handlers will be called.

classmethod is_registered()[source]

Return True if handlers are registered.

map(function, sequence, *v, **kw)[source]

Map function onto sequence until interrupted or done.

Interrupts will not occur inside function() unless forced.

classmethod register()[source]

Register the handlers so that signals can be suspended.

classmethod reset()[source]

Reset the signal logs and return last signal (signum, frame, time).

classmethod resume(signals=None)[source]

Resumes the specified signals.

classmethod set_signals(signals)[source]

Change the signal handlers.

Note: This does not change the signals listed in _suspended_signals list.

Parameters

signals (set()) – Set of signal numbers.

classmethod suspend(signals=None)[source]

Suspends the specified signals.

classmethod unregister(full=False)[source]

Reset handlers to the original values. No more signal suspension.

Parameters

full (bool) – If True, do a full reset, including counts.

mmfutils.contexts.coroutine(coroutine)[source]

Decorator for a context that yeilds an function from a coroutine.

This allows you to write functions that maintain state between calls. The use as a context here ensures that the coroutine is closed.

Examples

Here is an example based on that suggested by Thomas Kluyver: http://takluyver.github.io/posts/readable-python-coroutines.html

>>> @coroutine
... def get_have_seen(case_sensitive=False):
...     seen = set()    # Set of words already seen.  This is the "state"
...     word = (yield)
...     while True:
...         if not case_sensitive:
...             word = word.lower()
...         result = word in seen
...         seen.add(word)
...         word = (yield result)
>>> with get_have_seen(case_sensitive=False) as have_seen:
...     print(have_seen("hello"))
...     print(have_seen("hello"))
...     print(have_seen("Hello"))
...     print(have_seen("hi"))
...     print(have_seen("hi"))
False
True
True
False
True
>>> have_seen("hi")
Traceback (most recent call last):
   ...
StopIteration

You can also use this as a function (like open()) but don’t forget to close it.

>>> have_seen = get_have_seen(case_sensitive=True)
>>> have_seen("hello")
False
>>> have_seen("hello")
True
>>> have_seen("Hello")
False
>>> have_seen("hi")
False
>>> have_seen("hi")
True
>>> have_seen.close()
>>> have_seen("hi")
Traceback (most recent call last):
   ...
StopIteration
mmfutils.contexts.is_main_thread()[source]

Return True if this is the main thread.

mmfutils.contexts.nointerrupt(f)[source]

Decorator that suspends signals and passes an interrupted flag to the protected function. Can only be called from the main thread: will raise a RuntimeError otherwise (use @interrupted instead).

Examples

>>> @nointerrupt
... def f(interrupted):
...     for n in range(3):
...         if interrupted:
...             break
...         print(n)
...         time.sleep(0.1)
>>> f()
0
1
2