Documented undefined and implementation-defined behavior in Python

This is an older inquiry into whether Python as programming language allows undefined behavior (UD) in the same sense that it has in C and C++. That is, UD is cases of erroneous program behavior that is outside of the scope of the language facilities, which may be assumed by the interpreter to never occur, but if is reached, the effects (both desirable and undesirable) are not governed by its specification.

Implementation-defined behavior is not erroneous, but it is still not covered by the language standard. But at least various implementations cannot ignore its existence, but each of them must make up its mind about what to do in each case.

This page contains quotes to what evidence I have found in Python’s documentation.

My answer on StackOverflow: https://stackoverflow.com/a/75345325/530714

Ordered dictionaries in Python 3.6

Changed in version 3.7: Dictionary order is guaranteed to be insertion order. This behavior was an implementation detail of CPython from 3.6.

Not really UD, but a case where de-facto behavior became well-defined as the language evolved.

Mutating a list while iterating over it

This looks like a solid case of UD, since looking at lists while they are iterated over is simply not supposed to be used in a valid program.

CPython implementation detail: While a list is being sorted, the effect of attempting to mutate, or even inspect, the list is undefined. > The C implementation of Python makes the list appear empty for the duration, and raises ValueError if it can detect that the list has been mutated during a sort.

Bytecode

The whole dis module returns implementation-specific results.

Deletion via __del__

It is implementation-dependent whether __del__() is called a second time when a resurrected object is about to be destroyed

The way it is documentation leaves an impression that the whole thing is not very reliable and does not guarantee specific number of executions.

Identity of immutable types is implementation-specification

for immutable types, operations that compute new values may actually return a reference to any existing object with the same type and value, while for mutable objects this is not allowed. E.g., after a = 1; b = 1, a and b may or may not refer to the same object with the value one, depending on the implementation

This is not UD; comparing identities of objects is allowed.

Two UD in concurrent.futures

The first one looks to be a case of a fixed undefined behavior, i.e., something that newer versions of the language have changed to treat in a specified manner.

Changed in version 3.3: When one of the worker processes terminates abruptly, a BrokenProcessPool error is now raised. Previously, behaviour was undefined but operations on the executor or its futures would often freeze or deadlock.

The second one is an honest case of UD for add_done_callback():

If the callable raises a BaseException subclass, the behavior is undefined.

Here, package authors did not attempt to handle all situations that the callable object may create.

UD in email.parser

It is up to the users of the package to ensure that the protocol of interacting with BytesFeedParser is not violated and that a closed object is no longer fed data:

It is undefined what happens if feed() is called after this method has been called.

No touching closed streams in io.IOBase

Note that calling any method (even inquiries) on a closed stream is undefined.

This is clear UD: it is simply forbidden to do, and it is up to the client of the class to uphold.

The next sentence in the same specification is essentially a non-binding recommendation to implementations about what to do in this case:

Implementations may raise ValueError in this case.

But the earlier “undefined” already gave them freedom to return None, True, False, or to raise any sort of exception, or to set the computer on fire; the classic manifestations of undefined behavior.

seek() in io.TextIOBase leaks from POSIX

SEEK_SET or 0: seek from the start of the stream (the default); offset must either be a number returned by TextIOBase.tell(), or zero. Any other offset value produces undefined behaviour.

This feels like they implemented a wrapper around the underlying seek() from C, but did not bother to describe (and thus implement) validation of its inputs, and to assign behavior to invalid input combinations. This feels lazy.

time.pthread_getcpuclockid() exposes UD from underlying POSIX

Warning: Passing an invalid or expired thread_id may result in undefined behavior, such as segmentation fault.

Once again, the implementors chose to leave the burden of validating the method’s inputs to users of this method, which is a wrapper for host OS-specific time facility.


Written by Grigory Rechistov in Uncategorized on 27.08.2026. Tags: undefined behavior, implementation-defined python,


Copyright © 2026 Grigory Rechistov