text:
PEP: 687 Title: Isolating modules in the standard library Author: Erlend
Egeberg Aasland<EMAIL_ADDRESS>Petr Viktorin<EMAIL_ADDRESS>Discussions-To: https://discuss.python.org/t/14824 Status: Accepted
Type: Standards Track Content-Type: text/x-rst Requires: 489, 573, 630
Created: 04-Apr-2022 Python-Version: 3.12 Post-History: 04-Apr-2022,
11-Apr-2022 Resolution: https://discuss.python.org/t/14824/4

Abstract

Extensions in the standard library will be converted to multi-phase
initialization (PEP 489) and where possible, all state will be stored on
module objects rather than in process-global variables.

Note on Backdating

Much of this proposal has already been implemented. We submit this PEP
to explain the changes, seek consensus on whether they are good, propose
the remaining changes, and set best practices for new modules.

Motivation & Rationale

The informational PEP 630 describes the background, motivation,
rationale, implications and implementation notes of the proposed changes
as they apply generally to any extension module (not just the standard
library).

It is an integral part of this proposal. Read it first.

This PEP discusses specifics of the standard library.

Specification

The body of PEP 630 will be converted to a HOWTO in the Python
documentation, and that PEP will be retired (marked Final).

All extension modules in the standard library will be converted to
multi-phase initialization introduced in PEP 489.

All stdlib extension modules will be isolated. That is:

-   Types, functions and other objects defined by the module will either
    be immutable, or not shared with other module instances.

-   State specific to the module will not be shared with other module
    instances, unless it represents global state.

    For example, _csv.field_size_limit will get/set a module-specific
    number. On the other hand, functions like readline.get_history_item
    or os.getpid will continue to work with state that is process-global
    (external to the module, and possibly shared across other libraries,
    including non-Python ones).

Conversion to heap types

Static types that do not need module state access, and have no other
reason to be converted, should stay static.

Types whose methods need access to their module instance will be
converted to heap types following PEP 630, with the following
considerations:

-   All standard library types that used to be static types should
    remain immutable. Heap types must be defined with the
    Py_TPFLAGS_IMMUTABLE_TYPE flag to retain immutability. See
    bpo-43908.

    Tests should ensure TypeError is raised when trying to create a new
    attribute of an immutable type.

-   A static type with tp_new = NULL does not have a public constructor,
    but heap types inherit the constructor from the base class. Make
    sure types that previously were impossible to instantiate retain
    that feature; use Py_TPFLAGS_DISALLOW_INSTANTIATION. Add tests using
    test.support.check_disallow_instantiation(). See bpo-43916.

-   Converted heap types may unintentionally become serializable
    (pickle-able). Test that calling pickle.dumps has the same result
    before and after conversion, and if the test fails, add a __reduce__
    method that raises TypeError. See PR-21002 for an example.

These issues will be added to the Devguide to help any future
conversions.

If another kind of issue is found, the module in question should be
unchanged until a solution is found and added to the Devguide, and
already converted modules are checked and fixed.

Process

The following process should be added to the Devguide, and remain until
all modules are converted. Any new findings should be documented there
or in the general HOWTO.

Part 1: Preparation

1.  Open a discussion, either on the bug tracker or on Discourse.
    Involve the module maintainer and/or code owner. Explain the reason
    and rationale for the changes.
2.  Identify global state performance bottlenecks. Create a
    proof-of-concept implementation, and measure the performance impact.
    pyperf is a good tool for benchmarking.
3.  Create an implementation plan. For small modules with few types, a
    single PR may do the job. For larger modules with lots of types, and
    possibly also external library callbacks, multiple PR's will be
    needed.

Part 2: Implementation

Note: this is a suggested implementation plan for a complex module,
based on lessons learned with other modules. Feel free to simplify it
for smaller modules.

1.  Add Argument Clinic where possible; it enables you to easily use the
    defining class to fetch module state from type methods.
2.  Prepare for module state; establish a module state struct, add an
    instance as a static global variable, and create helper stubs for
    fetching the module state.
3.  Add relevant global variables to the module state struct, and modify
    code that accesses the global state to use the module state helpers
    instead. This step may be broken into several PR's.
4.  Where necessary, convert static types to heap types.
5.  Convert the global module state struct to true module state.
6.  Implement multi-phase initialisation.

Steps 4 through 6 should preferably land in a single alpha development
phase.

Backwards Compatibility

Extension modules in the standard library will now be loadable more than
once. For example, deleting such a module from sys.modules and
re-importing it will result in a fresh module instance, isolated from
any previously loaded instances.

This may affect code that expected the previous behavior: globals of
extension modules were shallowly copied from the first loaded module.

Security Implications

None known.

How to Teach This

A large part of this proposal is a HOWTO aimed at experienced users,
which will be moved to the documentation.

Beginners should not be affected.

Reference Implementation

Most of the changes are now in the main branch, as commits for these
issues:

-   bpo-40077, Convert static types to heap types: use PyType_FromSpec()
-   bpo-46417, Clear static types in Py_Finalize() for embedded Python
-   bpo-1635741, Py_Finalize() doesn't clear all Python objects at exit

As an example, changes and fix-ups done in the _csv module are:

-   GH-23224, Remove static state from the _csv module
-   GH-26008, Allow subclassing of csv.Error
-   GH-26074, Add GC support to _csv heap types
-   GH-26351, Make heap types converted during 3.10 alpha immutable

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

authors:
Erlend Egeberg Aasland
Petr Viktorin


text:
PEP: 3141 Title: A Type Hierarchy for Numbers Version: $Revision$
Last-Modified: $Date$ Author: Jeffrey Yasskin<EMAIL_ADDRESS>Status: Final Type: Standards Track Content-Type: text/x-rst Created:
23-Apr-2007 Python-Version: 3.0 Post-History: 25-Apr-2007, 16-May-2007,
02-Aug-2007

Abstract

This proposal defines a hierarchy of Abstract Base Classes (ABCs) (PEP
3119) to represent number-like classes. It proposes a hierarchy of
Number :> Complex :> Real :> Rational :> Integral where A :> B means "A
is a supertype of B". The hierarchy is inspired by Scheme's numeric
tower[1].

Rationale

Functions that take numbers as arguments should be able to determine the
properties of those numbers, and if and when overloading based on types
is added to the language, should be overloadable based on the types of
the arguments. For example, slicing requires its arguments to be
Integrals, and the functions in the math module require their arguments
to be Real.

Specification

This PEP specifies a set of Abstract Base Classes, and suggests a
general strategy for implementing some of the methods. It uses
terminology from PEP 3119, but the hierarchy is intended to be
meaningful for any systematic method of defining sets of classes.

The type checks in the standard library should use these classes instead
of the concrete built-ins.

Numeric Classes

We begin with a Number class to make it easy for people to be fuzzy
about what kind of number they expect. This class only helps with
overloading; it doesn't provide any operations. :

    class Number(metaclass=ABCMeta): pass

Most implementations of complex numbers will be hashable, but if you
need to rely on that, you'll have to check it explicitly: mutable
numbers are supported by this hierarchy. :

    class Complex(Number):
        """Complex defines the operations that work on the builtin complex type.

        In short, those are: conversion to complex, bool(), .real, .imag,
        +, -, *, /, **, abs(), .conjugate(), ==, and !=.

        If it is given heterogeneous arguments, and doesn't have special
        knowledge about them, it should fall back to the builtin complex
        type as described below.
        """

        @abstractmethod
        def __complex__(self):
            """Return a builtin complex instance."""

        def __bool__(self):
            """True if self != 0."""
            return self != 0

        @abstractproperty
        def real(self):
            """Retrieve the real component of this number.

            This should subclass Real.
            """
            raise NotImplementedError

        @abstractproperty
        def imag(self):
            """Retrieve the imaginary component of this number.

            This should subclass Real.
            """
            raise NotImplementedError

        @abstractmethod
        def __add__(self, other):
            raise NotImplementedError

        @abstractmethod
        def __radd__(self, other):
            raise NotImplementedError

        @abstractmethod
        def __neg__(self):
            raise NotImplementedError

        def __pos__(self):
            """Coerces self to whatever class defines the method."""
            raise NotImplementedError

        def __sub__(self, other):
            return self + -other

        def __rsub__(self, other):
            return -self + other

        @abstractmethod
        def __mul__(self, other):
            raise NotImplementedError

        @abstractmethod
        def __rmul__(self, other):
            raise NotImplementedError

        @abstractmethod
        def __div__(self, other):
            """a/b; should promote to float or complex when necessary."""
            raise NotImplementedError

        @abstractmethod
        def __rdiv__(self, other):
            raise NotImplementedError

        @abstractmethod
        def __pow__(self, exponent):
            """a**b; should promote to float or complex when necessary."""
            raise NotImplementedError

        @abstractmethod
        def __rpow__(self, base):
            raise NotImplementedError

        @abstractmethod
        def __abs__(self):
            """Returns the Real distance from 0."""
            raise NotImplementedError

        @abstractmethod
        def conjugate(self):
            """(x+y*i).conjugate() returns (x-y*i)."""
            raise NotImplementedError

        @abstractmethod
        def __eq__(self, other):
            raise NotImplementedError

        # __ne__ is inherited from object and negates whatever __eq__ does.

The Real ABC indicates that the value is on the real line, and supports
the operations of the float builtin. Real numbers are totally ordered
except for NaNs (which this PEP basically ignores). :

    class Real(Complex):
        """To Complex, Real adds the operations that work on real numbers.

        In short, those are: conversion to float, trunc(), math.floor(),
        math.ceil(), round(), divmod(), //, %, <, <=, >, and >=.

        Real also provides defaults for some of the derived operations.
        """

        # XXX What to do about the __int__ implementation that's
        # currently present on float?  Get rid of it?

        @abstractmethod
        def __float__(self):
            """Any Real can be converted to a native float object."""
            raise NotImplementedError

        @abstractmethod
        def __trunc__(self):
            """Truncates self to an Integral.

            Returns an Integral i such that:
              * i>=0 iff self>0;
              * abs(i) <= abs(self);
              * for any Integral j satisfying the first two conditions,
                abs(i) >= abs(j) [i.e. i has "maximal" abs among those].
            i.e. "truncate towards 0".
            """
            raise NotImplementedError

        @abstractmethod
        def __floor__(self):
            """Finds the greatest Integral <= self."""
            raise NotImplementedError

        @abstractmethod
        def __ceil__(self):
            """Finds the least Integral >= self."""
            raise NotImplementedError

        @abstractmethod
        def __round__(self, ndigits:Integral=None):
            """Rounds self to ndigits decimal places, defaulting to 0.

            If ndigits is omitted or None, returns an Integral,
            otherwise returns a Real, preferably of the same type as
            self. Types may choose which direction to round half. For
            example, float rounds half toward even.

            """
            raise NotImplementedError

        def __divmod__(self, other):
            """The pair (self // other, self % other).

            Sometimes this can be computed faster than the pair of
            operations.
            """
            return (self // other, self % other)

        def __rdivmod__(self, other):
            """The pair (self // other, self % other).

            Sometimes this can be computed faster than the pair of
            operations.
            """
            return (other // self, other % self)

        @abstractmethod
        def __floordiv__(self, other):
            """The floor() of self/other. Integral."""
            raise NotImplementedError

        @abstractmethod
        def __rfloordiv__(self, other):
            """The floor() of other/self."""
            raise NotImplementedError

        @abstractmethod
        def __mod__(self, other):
            """self % other

            See
            https://mail.python.org/pipermail/python-3000/2006-May/001735.html
            and consider using "self/other - trunc(self/other)"
            instead if you're worried about round-off errors.
            """
            raise NotImplementedError

        @abstractmethod
        def __rmod__(self, other):
            """other % self"""
            raise NotImplementedError

        @abstractmethod
        def __lt__(self, other):
            """< on Reals defines a total ordering, except perhaps for NaN."""
            raise NotImplementedError

        @abstractmethod
        def __le__(self, other):
            raise NotImplementedError

        # __gt__ and __ge__ are automatically done by reversing the arguments.
        # (But __le__ is not computed as the opposite of __gt__!)

        # Concrete implementations of Complex abstract methods.
        # Subclasses may override these, but don't have to.

        def __complex__(self):
            return complex(float(self))

        @property
        def real(self):
            return +self

        @property
        def imag(self):
            return 0

        def conjugate(self):
            """Conjugate is a no-op for Reals."""
            return +self

We should clean up Demo/classes/Rat.py and promote it into rational.py
in the standard library. Then it will implement the Rational ABC. :

    class Rational(Real, Exact):
        """.numerator and .denominator should be in lowest terms."""

        @abstractproperty
        def numerator(self):
            raise NotImplementedError

        @abstractproperty
        def denominator(self):
            raise NotImplementedError

        # Concrete implementation of Real's conversion to float.
        # (This invokes Integer.__div__().)

        def __float__(self):
            return self.numerator / self.denominator

And finally integers:

    class Integral(Rational):
        """Integral adds a conversion to int and the bit-string operations."""

        @abstractmethod
        def __int__(self):
            raise NotImplementedError

        def __index__(self):
            """__index__() exists because float has __int__()."""
            return int(self)

        def __lshift__(self, other):
            return int(self) << int(other)

        def __rlshift__(self, other):
            return int(other) << int(self)

        def __rshift__(self, other):
            return int(self) >> int(other)

        def __rrshift__(self, other):
            return int(other) >> int(self)

        def __and__(self, other):
            return int(self) & int(other)

        def __rand__(self, other):
            return int(other) & int(self)

        def __xor__(self, other):
            return int(self) ^ int(other)

        def __rxor__(self, other):
            return int(other) ^ int(self)

        def __or__(self, other):
            return int(self) | int(other)

        def __ror__(self, other):
            return int(other) | int(self)

        def __invert__(self):
            return ~int(self)

        # Concrete implementations of Rational and Real abstract methods.
        def __float__(self):
            """float(self) == float(int(self))"""
            return float(int(self))

        @property
        def numerator(self):
            """Integers are their own numerators."""
            return +self

        @property
        def denominator(self):
            """Integers have a denominator of 1."""
            return 1

Changes to operations and __magic__ methods

To support more precise narrowing from float to int (and more generally,
from Real to Integral), we propose the following new __magic__ methods,
to be called from the corresponding library functions. All of these
return Integrals rather than Reals.

1.  __trunc__(self), called from a new builtin trunc(x), which returns
    the Integral closest to x between 0 and x.
2.  __floor__(self), called from math.floor(x), which returns the
    greatest Integral <= x.
3.  __ceil__(self), called from math.ceil(x), which returns the least
    Integral >= x.
4.  __round__(self), called from round(x), which returns the Integral
    closest to x, rounding half as the type chooses. float will change
    in 3.0 to round half toward even. There is also a 2-argument
    version, __round__(self, ndigits), called from round(x, ndigits),
    which should return a Real.

In 2.6, math.floor, math.ceil, and round will continue to return floats.

The int() conversion implemented by float is equivalent to trunc(). In
general, the int() conversion should try __int__() first and if it is
not found, try __trunc__().

complex.__{divmod,mod,floordiv,int,float}__ also go away. It would be
nice to provide a nice error message to help confused porters, but not
appearing in help(complex) is more important.

Notes for type implementors

Implementors should be careful to make equal numbers equal and hash them
to the same values. This may be subtle if there are two different
extensions of the real numbers. For example, a complex type could
reasonably implement hash() as follows:

    def __hash__(self):
        return hash(complex(self))

but should be careful of any values that fall outside of the built in
complex's range or precision.

Adding More Numeric ABCs

There are, of course, more possible ABCs for numbers, and this would be
a poor hierarchy if it precluded the possibility of adding those. You
can add MyFoo between Complex and Real with:

    class MyFoo(Complex): ...
    MyFoo.register(Real)

Implementing the arithmetic operations

We want to implement the arithmetic operations so that mixed-mode
operations either call an implementation whose author knew about the
types of both arguments, or convert both to the nearest built in type
and do the operation there. For subtypes of Integral, this means that
__add__ and __radd__ should be defined as:

    class MyIntegral(Integral):

        def __add__(self, other):
            if isinstance(other, MyIntegral):
                return do_my_adding_stuff(self, other)
            elif isinstance(other, OtherTypeIKnowAbout):
                return do_my_other_adding_stuff(self, other)
            else:
                return NotImplemented

        def __radd__(self, other):
            if isinstance(other, MyIntegral):
                return do_my_adding_stuff(other, self)
            elif isinstance(other, OtherTypeIKnowAbout):
                return do_my_other_adding_stuff(other, self)
            elif isinstance(other, Integral):
                return int(other) + int(self)
            elif isinstance(other, Real):
                return float(other) + float(self)
            elif isinstance(other, Complex):
                return complex(other) + complex(self)
            else:
                return NotImplemented

There are 5 different cases for a mixed-type operation on subclasses of
Complex. I'll refer to all of the above code that doesn't refer to
MyIntegral and OtherTypeIKnowAbout as "boilerplate". a will be an
instance of A, which is a subtype of Complex (a : A <: Complex), and
b : B <: Complex. I'll consider a + b:

1.  If A defines an __add__ which accepts b, all is well.
2.  If A falls back to the boilerplate code, and it were to return a
    value from __add__, we'd miss the possibility that B defines a more
    intelligent __radd__, so the boilerplate should return
    NotImplemented from __add__. (Or A may not implement __add__ at
    all.)
3.  Then B's __radd__ gets a chance. If it accepts a, all is well.
4.  If it falls back to the boilerplate, there are no more possible
    methods to try, so this is where the default implementation should
    live.
5.  If B <: A, Python tries B.__radd__ before A.__add__. This is ok,
    because it was implemented with knowledge of A, so it can handle
    those instances before delegating to Complex.

If A<:Complex and B<:Real without sharing any other knowledge, then the
appropriate shared operation is the one involving the built in complex,
and both __radd__s land there, so a+b == b+a.

Rejected Alternatives

The initial version of this PEP defined an algebraic hierarchy inspired
by a Haskell Numeric Prelude[2] including MonoidUnderPlus,
AdditiveGroup, Ring, and Field, and mentioned several other possible
algebraic types before getting to the numbers. We had expected this to
be useful to people using vectors and matrices, but the NumPy community
really wasn't interested, and we ran into the issue that even if x is an
instance of X <: MonoidUnderPlus and y is an instance of
Y <: MonoidUnderPlus, x + y may still not make sense.

Then we gave the numbers a much more branching structure to include
things like the Gaussian Integers and Z/nZ, which could be Complex but
wouldn't necessarily support things like division. The community decided
that this was too much complication for Python, so I've now scaled back
the proposal to resemble the Scheme numeric tower much more closely.

The Decimal Type

After consultation with its authors it has been decided that the Decimal
type should not at this time be made part of the numeric tower.

References

Acknowledgements

Thanks to Neal Norwitz for encouraging me to write this PEP in the first
place, to Travis Oliphant for pointing out that the numpy people didn't
really care about the algebraic concepts, to Alan Isaac for reminding me
that Scheme had already done this, and to Guido van Rossum and lots of
other people on the mailing list for refining the concept.

Copyright

This document has been placed in the public domain.

  Local Variables: mode: indented-text indent-tabs-mode: nil
  sentence-end-double-space: t fill-column: 70 coding: utf-8 End:

[1] The Scheme numerical tower
(https://groups.csail.mit.edu/mac/ftpdir/scheme-reports/r5rs-html/r5rs_8.html#SEC50)

[2] NumericPrelude: An experimental alternative hierarchy of numeric
type classes
(https://archives.haskell.org/code.haskell.org/numeric-prelude/docs/html/index.html)

license: Public Domain


text:
PEP: 259 Title: Omit printing newline after newline Author: Guido van
Rossum<EMAIL_ADDRESS>Status: Rejected Type: Standards Track
Content-Type: text/x-rst Created: 11-Jun-2001 Python-Version: 2.2
Post-History: 11-Jun-2001

Abstract

Currently, the print statement always appends a newline, unless a
trailing comma is used. This means that if we want to print data that
already ends in a newline, we get two newlines, unless special
precautions are taken.

I propose to skip printing the newline when it follows a newline that
came from data.

In order to avoid having to add yet another magic variable to file
objects, I propose to give the existing 'softspace' variable an extra
meaning: a negative value will mean "the last data written ended in a
newline so no space or newline is required."

Problem

When printing data that resembles the lines read from a file using a
simple loop, double-spacing occurs unless special care is taken:

    >>> for line in open("/etc/passwd").readlines():
    ... print line
    ...
    root:x:0:0:root:/root:/bin/bash

    bin:x:1:1:bin:/bin:

    daemon:x:2:2:daemon:/sbin:

    (etc.)

    >>>

While there are easy work-arounds, this is often noticed only during
testing and requires an extra edit-test roundtrip; the fixed code is
uglier and harder to maintain.

Proposed Solution

In the PRINT_ITEM opcode in ceval.c, when a string object is printed, a
check is already made that looks at the last character of that string.
Currently, if that last character is a whitespace character other than
space, the softspace flag is reset to zero; this suppresses the space
between two items if the first item is a string ending in newline, tab,
etc. (but not when it ends in a space). Otherwise the softspace flag is
set to one.

The proposal changes this test slightly so that softspace is set to:

-   -1 -- if the last object written is a string ending in a newline
-   0 -- if the last object written is a string ending in a whitespace
    character that's neither space nor newline
-   1 -- in all other cases (including the case when the last object
    written is an empty string or not a string)

Then, the PRINT_NEWLINE opcode, printing of the newline is suppressed if
the value of softspace is negative; in any case the softspace flag is
reset to zero.

Scope

This only affects printing of 8-bit strings. It doesn't affect Unicode,
although that could be considered a bug in the Unicode implementation.
It doesn't affect other objects whose string representation happens to
end in a newline character.

Risks

This change breaks some existing code. For example:

    print "Subject: PEP 259\n"
    print message_body

In current Python, this produces a blank line separating the subject
from the message body; with the proposed change, the body begins
immediately below the subject. This is not very robust code anyway; it
is better written as:

    print "Subject: PEP 259"
    print
    print message_body

In the test suite, only test_StringIO (which explicitly tests for this
feature) breaks.

Implementation

A patch relative to current CVS is here:

    http://sourceforge.net/tracker/index.php?func=detail&aid=432183&group_id=5470&atid=305470

Rejected

The user community unanimously rejected this, so I won't pursue this
idea any further. Frequently heard arguments against included:

-   It is likely to break thousands of CGI scripts.
-   Enough magic already (also: no more tinkering with 'print' please).

Copyright

This document has been placed in the public domain.


text:
PEP: 585 Title: Type Hinting Generics In Standard Collections Version:
$Revision$ Last-Modified: $Date$ Author: Łukasz Langa
<EMAIL_ADDRESS>Discussions-To<EMAIL_ADDRESS>Status: Final
Type: Standards Track Topic: Typing Content-Type: text/x-rst Created:
03-Mar-2019 Python-Version: 3.9 Resolution:
<EMAIL_ADDRESS>
python:types-genericalias and the documentation for
~object.__class_getitem__

Abstract

Static typing as defined by PEPs 484, 526, 544, 560, and 563 was built
incrementally on top of the existing Python runtime and constrained by
existing syntax and runtime behavior. This led to the existence of a
duplicated collection hierarchy in the typing module due to generics
(for example typing.List and the built-in list).

This PEP proposes to enable support for the generics syntax in all
standard collections currently available in the typing module.

Rationale and Goals

This change removes the necessity for a parallel type hierarchy in the
typing module, making it easier for users to annotate their programs and
easier for teachers to teach Python.

Terminology

Generic (n.) -- a type that can be parameterized, typically a container.
Also known as a parametric type or a generic type. For example: dict.

parameterized generic -- a specific instance of a generic with the
expected types for container elements provided. Also known as a
parameterized type. For example: dict[str, int].

Backwards compatibility

Tooling, including type checkers and linters, will have to be adapted to
recognize standard collections as generics.

On the source level, the newly described functionality requires Python
3.9. For use cases restricted to type annotations, Python files with the
"annotations" future-import (available since Python 3.7) can
parameterize standard collections, including builtins. To reiterate,
that depends on the external tools understanding that this is valid.

Implementation

Starting with Python 3.7, when from __future__ import annotations is
used, function and variable annotations can parameterize standard
collections directly. Example:

    from __future__ import annotations

    def find(haystack: dict[str, list[int]]) -> int:
        ...

Usefulness of this syntax before PEP 585 is limited as external tooling
like Mypy does not recognize standard collections as generic. Moreover,
certain features of typing like type aliases or casting require putting
types outside of annotations, in runtime context. While these are
relatively less common than type annotations, it's important to allow
using the same type syntax in all contexts. This is why starting with
Python 3.9, the following collections become generic using
__class_getitem__() to parameterize contained types:

-   tuple # typing.Tuple
-   list # typing.List
-   dict # typing.Dict
-   set # typing.Set
-   frozenset # typing.FrozenSet
-   type # typing.Type
-   collections.deque
-   collections.defaultdict
-   collections.OrderedDict
-   collections.Counter
-   collections.ChainMap
-   collections.abc.Awaitable
-   collections.abc.Coroutine
-   collections.abc.AsyncIterable
-   collections.abc.AsyncIterator
-   collections.abc.AsyncGenerator
-   collections.abc.Iterable
-   collections.abc.Iterator
-   collections.abc.Generator
-   collections.abc.Reversible
-   collections.abc.Container
-   collections.abc.Collection
-   collections.abc.Callable
-   collections.abc.Set # typing.AbstractSet
-   collections.abc.MutableSet
-   collections.abc.Mapping
-   collections.abc.MutableMapping
-   collections.abc.Sequence
-   collections.abc.MutableSequence
-   collections.abc.ByteString
-   collections.abc.MappingView
-   collections.abc.KeysView
-   collections.abc.ItemsView
-   collections.abc.ValuesView
-   contextlib.AbstractContextManager # typing.ContextManager
-   contextlib.AbstractAsyncContextManager # typing.AsyncContextManager
-   re.Pattern # typing.Pattern, typing.re.Pattern
-   re.Match # typing.Match, typing.re.Match

Importing those from typing is deprecated. Due to PEP 563 and the
intention to minimize the runtime impact of typing, this deprecation
will not generate DeprecationWarnings. Instead, type checkers may warn
about such deprecated usage when the target version of the checked
program is signalled to be Python 3.9 or newer. It's recommended to
allow for those warnings to be silenced on a project-wide basis.

The deprecated functionality may eventually be removed from the typing
module. Removal will occur no sooner than Python 3.9's end of life,
scheduled for October 2025.

Parameters to generics are available at runtime

Preserving the generic type at runtime enables introspection of the type
which can be used for API generation or runtime type checking. Such
usage is already present in the wild.

Just like with the typing module today, the parameterized generic types
listed in the previous section all preserve their type parameters at
runtime:

    >>> list[str]
    list[str]
    >>> tuple[int, ...]
    tuple[int, ...]
    >>> ChainMap[str, list[str]]
    collections.ChainMap[str, list[str]]

This is implemented using a thin proxy type that forwards all method
calls and attribute accesses to the bare origin type with the following
exceptions:

-   the __repr__ shows the parameterized type;
-   the __origin__ attribute points at the non-parameterized generic
    class;
-   the __args__ attribute is a tuple (possibly of length
    1)  of generic types passed to the original __class_getitem__;
-   the __parameters__ attribute is a lazily computed tuple (possibly
    empty) of unique type variables found in __args__;
-   the __getitem__ raises an exception to disallow mistakes like
    dict[str][str]. However it allows e.g. dict[str, T][int] and in that
    case returns dict[str, int].

This design means that it is possible to create instances of
parameterized collections, like:

    >>> l = list[str]()
    []
    >>> list is list[str]
    False
    >>> list == list[str]
    False
    >>> list[str] == list[str]
    True
    >>> list[str] == list[int]
    False
    >>> isinstance([1, 2, 3], list[str])
    TypeError: isinstance() arg 2 cannot be a parameterized generic
    >>> issubclass(list, list[str])
    TypeError: issubclass() arg 2 cannot be a parameterized generic
    >>> isinstance(list[str], types.GenericAlias)
    True

Objects created with bare types and parameterized types are exactly the
same. The generic parameters are not preserved in instances created with
parameterized types, in other words generic types erase type parameters
during object creation.

One important consequence of this is that the interpreter does not
attempt to type check operations on the collection created with a
parameterized type. This provides symmetry between:

    l: list[str] = []

and:

    l = list[str]()

For accessing the proxy type from Python code, it will be exported from
the types module as GenericAlias.

Pickling or (shallow- or deep-) copying a GenericAlias instance will
preserve the type, origin, attributes and parameters.

Forward compatibility

Future standard collections must implement the same behavior.

Reference implementation

A proof-of-concept or prototype implementation exists.

Rejected alternatives

Do nothing

Keeping the status quo forces Python programmers to perform book-keeping
of imports from the typing module for standard collections, making all
but the simplest annotations cumbersome to maintain. The existence of
parallel types is confusing to newcomers (why is there both list and
List?).

The above problems also don't exist in user-built generic classes which
share runtime functionality and the ability to use them as generic type
annotations. Making standard collections harder to use in type hinting
from user classes hindered typing adoption and usability.

Generics erasure

It would be easier to implement __class_getitem__ on the listed standard
collections in a way that doesn't preserve the generic type, in other
words:

    >>> list[str]
    <class 'list'>
    >>> tuple[int, ...]
    <class 'tuple'>
    >>> collections.ChainMap[str, list[str]]
    <class 'collections.ChainMap'>

This is problematic as it breaks backwards compatibility: current
equivalents of those types in the typing module do preserve the generic
type:

    >>> from typing import List, Tuple, ChainMap
    >>> List[str]
    typing.List[str]
    >>> Tuple[int, ...]
    typing.Tuple[int, ...]
    >>> ChainMap[str, List[str]]
    typing.ChainMap[str, typing.List[str]]

As mentioned in the "Implementation" section, preserving the generic
type at runtime enables runtime introspection of the type which can be
used for API generation or runtime type checking. Such usage is already
present in the wild.

Additionally, implementing subscripts as identity functions would make
Python less friendly to beginners. Say, if a user is mistakenly passing
a list type instead of a list object to a function, and that function is
indexing the received object, the code would no longer raise an error.

Today:

    >>> l = list
    >>> l[-1]
    TypeError: 'type' object is not subscriptable

With __class_getitem__ as an identity function:

    >>> l = list
    >>> l[-1]
    list

The indexing being successful here would likely end up raising an
exception at a distance, confusing the user.

Disallowing instantiation of parameterized types

Given that the proxy type which preserves __origin__ and __args__ is
mostly useful for runtime introspection purposes, we might have
disallowed instantiation of parameterized types.

In fact, forbidding instantiation of parameterized types is what the
typing module does today for types which parallel builtin collections
(instantiation of other parameterized types is allowed).

The original reason for this decision was to discourage spurious
parameterization which made object creation up to two orders of
magnitude slower compared to the special syntax available for those
builtin collections.

This rationale is not strong enough to allow the exceptional treatment
of builtins. All other parameterized types can be instantiated,
including parallels of collections in the standard library. Moreover,
Python allows for instantiation of lists using list() and some builtin
collections don't provide special syntax for instantiation.

Making isinstance(obj, list[str]) perform a check ignoring generics

An earlier version of this PEP suggested treating parameterized generics
like list[str] as equivalent to their non-parameterized variants like
list for purposes of isinstance() and issubclass(). This would be
symmetrical to how list[str]() creates a regular list.

This design was rejected because isinstance() and issubclass() checks
with parameterized generics would read like element-by-element runtime
type checks. The result of those checks would be surprising, for
example:

    >>> isinstance([1, 2, 3], list[str])
    True

Note the object doesn't match the provided generic type but isinstance()
still returns True because it only checks whether the object is a list.

If a library is faced with a parameterized generic and would like to
perform an isinstance() check using the base type, that type can be
retrieved using the __origin__ attribute on the parameterized generic.

Making isinstance(obj, list[str]) perform a runtime type check

This functionality requires iterating over the collection which is a
destructive operation in some of them. This functionality would have
been useful, however implementing the type checker within Python that
would deal with complex types, nested type checking, type variables,
string forward references, and so on is out of scope for this PEP.

Naming the type GenericType instead of GenericAlias

We considered a different name for this type, but decided GenericAlias
is better -- these aren't real types, they are aliases for the
corresponding container type with some extra metadata attached.

Note on the initial draft

An early version of this PEP discussed matters beyond generics in
standard collections. Those unrelated topics were removed for clarity.

Acknowledgments

Thank you to Guido van Rossum for his work on Python, and the
implementation of this PEP specifically.

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.


text:
PEP: 10 Title: Voting Guidelines Author: Barry Warsaw<EMAIL_ADDRESS>Status: Active Type: Process Content-Type: text/x-rst Created:
07-Mar-2002 Post-History: 07-Mar-2002

Abstract

This PEP outlines the python-dev voting guidelines. These guidelines
serve to provide feedback or gauge the "wind direction" on a particular
proposal, idea, or feature. They don't have a binding force.

Rationale

When a new idea, feature, patch, etc. is floated in the Python
community, either through a PEP or on the mailing lists (most likely on
python-dev[1]), it is sometimes helpful to gauge the community's general
sentiment. Sometimes people just want to register their opinion of an
idea. Sometimes the BDFL wants to take a straw poll. Whatever the
reason, these guidelines have been adopted so as to provide a common
language for developers.

While opinions are (sometimes) useful, but they are never binding.
Opinions that are accompanied by rationales are always valued higher
than bare scores (this is especially true with -1 votes).

Voting Scores

The scoring guidelines are loosely derived from the Apache voting
procedure[2], with of course our own spin on things. There are 4
possible vote scores:

-   +1 I like it
-   +0 I don't care, but go ahead
-   -0 I don't care, so why bother?
-   -1 I hate it

You may occasionally see wild flashes of enthusiasm (either for or
against) with vote scores like +2, +1000, or -1000. These aren't really
valued much beyond the above scores, but it's nice to see people get
excited about such geeky stuff.

References

Copyright

This document has been placed in the public domain.

[1] Python Developer's Guide, (http://www.python.org/dev/)

[2] Apache Project Guidelines and Voting Rules
(http://httpd.apache.org/dev/guidelines.html)


text:
PEP: 678 Title: Enriching Exceptions with Notes Author: Zac
Hatfield-Dodds<EMAIL_ADDRESS>Sponsor: Irit Katriel Discussions-To:
https://discuss.python.org/t/pep-678-enriching-exceptions-with-notes/13374
Status: Final Type: Standards Track Content-Type: text/x-rst Requires:
654 Created: 20-Dec-2021 Python-Version: 3.11 Post-History: 27-Jan-2022
Resolution:
https://discuss.python.org/t/pep-678-enriching-exceptions-with-notes/13374/100

python:BaseException.add_note and python:BaseException.__notes__

See python:tut-exception-notes for a user-focused tutorial.

Abstract

Exception objects are typically initialized with a message that
describes the error which has occurred. Because further information may
be available when the exception is caught and re-raised, or included in
an ExceptionGroup, this PEP proposes to add
BaseException.add_note(note), a .__notes__ attribute holding a list of
notes so added, and to update the builtin traceback formatting code to
include notes in the formatted traceback following the exception string.

This is particularly useful in relation to PEP 654 ExceptionGroups,
which make previous workarounds ineffective or confusing. Use cases have
been identified in the standard library, Hypothesis and cattrs packages,
and common code patterns with retries.

Motivation

When an exception is created in order to be raised, it is usually
initialized with information that describes the error that has occurred.
There are cases where it is useful to add information after the
exception was caught. For example,

-   testing libraries may wish to show the values involved in a failing
    assertion, or the steps to reproduce a failure (e.g. pytest and
    hypothesis; example below).
-   code which retries an operation on error may wish to associate an
    iteration, timestamp, or other explanation with each of several
    errors - especially if re-raising them in an ExceptionGroup.
-   programming environments for novices can provide more detailed
    descriptions of various errors, and tips for resolving them.

Existing approaches must pass this additional information around while
keeping it in sync with the state of raised, and potentially caught or
chained, exceptions. This is already error-prone, and made more
difficult by PEP 654 ExceptionGroups, so the time is right for a
built-in solution. We therefore propose to add:

-   a new method BaseException.add_note(note: str),
-   BaseException.__notes__, a list of note strings added using
    .add_note(), and
-   support in the builtin traceback formatting code such that notes are
    displayed in the formatted traceback following the exception string.

Example usage

    >>> try:
    ...     raise TypeError('bad type')
    ... except Exception as e:
    ...     e.add_note('Add some information')
    ...     raise
    ...
    Traceback (most recent call last):
      File "<stdin>", line 2, in <module>
    TypeError: bad type
    Add some information
    >>>

When collecting exceptions into an exception group, we may want to add
context information for the individual errors. In the following example
with Hypothesis' proposed support for ExceptionGroup, each exception
includes a note of the minimal failing example:

    from hypothesis import given, strategies as st, target

    @given(st.integers())
    def test(x):
        assert x < 0
        assert x > 0

    + Exception Group Traceback (most recent call last):
    |   File "test.py", line 4, in test
    |     def test(x):
    |
    |   File "hypothesis/core.py", line 1202, in wrapped_test
    |     raise the_error_hypothesis_found
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    | ExceptionGroup: Hypothesis found 2 distinct failures.
    +-+---------------- 1 ----------------
        | Traceback (most recent call last):
        |   File "test.py", line 6, in test
        |     assert x > 0
        |     ^^^^^^^^^^^^
        | AssertionError: assert -1 > 0
        |
        | Falsifying example: test(
        |     x=-1,
        | )
        +---------------- 2 ----------------
        | Traceback (most recent call last):
        |   File "test.py", line 5, in test
        |     assert x < 0
        |     ^^^^^^^^^^^^
        | AssertionError: assert 0 < 0
        |
        | Falsifying example: test(
        |     x=0,
        | )
        +------------------------------------

Non-goals

Tracking multiple notes as a list, rather than by concatenating strings
when notes are added, is intended to maintain the distinction between
the individual notes. This might be required in specialized use cases,
such as translation of the notes by packages like friendly-traceback.

However, __notes__ is not intended to carry structured data. If your
note is for use by a program rather than display to a human, we
recommend instead (or additionally) choosing a convention for an
attribute, e.g. err._parse_errors = ... on the error or ExceptionGroup.

As a rule of thumb, we suggest that you should prefer exception chaining
when the error is going to be re-raised or handled as an individual
error, and prefer .add_note() when you want to avoid changing the
exception type or are collecting multiple exception objects to handle
together.[1]

Specification

BaseException gains a new method .add_note(note: str). If note is a
string, .add_note(note) appends it to the __notes__ list, creating the
attribute if it does not already exist. If note is not a string,
.add_note() raises TypeError.

Libraries may clear existing notes by modifying or deleting the
__notes__ list, if it has been created, including clearing all notes
with del err.__notes__. This allows full control over the attached
notes, without overly complicating the API or adding multiple names to
BaseException.__dict__.

When an exception is displayed by the interpreter's builtin
traceback-rendering code, its notes (if there are any) appear
immediately after the exception message, in the order in which they were
added, with each note starting on a new line.

If __notes__ has been created, BaseExceptionGroup.subgroup and
BaseExceptionGroup.split create a new list for each new instance,
containing the same contents as the original exception group's
__notes__.

We do not specify the expected behaviour when users have assigned a
non-list value to __notes__, or a list which contains non-string
elements. Implementations might choose to emit warnings, discard or
ignore bad values, convert them to strings, raise an exception, or do
something else entirely.

Backwards Compatibility

System-defined or "dunder" names (following the pattern __*__) are part
of the language specification, with unassigned names reserved for future
use and subject to breakage without warning. We are also unaware of any
code which would be broken by adding __notes__.

We were also unable to find any code which would be broken by the
addition of BaseException.add_note(): while searching Google and GitHub
finds several definitions of an .add_note() method, none of them are on
a subclass of BaseException.

How to Teach This

The add_note() method and __notes__ attribute will be documented as part
of the language standard, and explained as part of the "Errors and
Exceptions" tutorial.

Reference Implementation

Following discussions related to PEP 654[2], an early version of this
proposal was implemented in and released in CPython 3.11.0a3, with a
mutable string-or-none __note__ attribute.

CPython PR #31317 implements .add_note() and __notes__.

Rejected Ideas

Use print() (or logging, etc.)

Reporting explanatory or contextual information about an error by
printing or logging has historically been an acceptable workaround.
However, we dislike the way this separates the content from the
exception object it refers to -which can lead to "orphan" reports if the
error was caught and handled later, or merely significant difficulties
working out which explanation corresponds to which error. The new
ExceptionGroup type intensifies these existing challenges.

Keeping the __notes__ attached to the exception object, in the same way
as the __traceback__ attribute, eliminates these problems.

raise Wrapper(explanation) from err

An alternative pattern is to use exception chaining: by raising a
'wrapper' exception containing the context or explanation from the
current exception, we avoid the separation challenges from print().
However, this has two key problems.

First, it changes the type of the exception, which is often a breaking
change for downstream code. We consider always raising a Wrapper
exception unacceptably inelegant; but because custom exception types
might have any number of required arguments we can't always create an
instance of the same type with our explanation. In cases where the exact
exception type is known this can work, such as the standard library
http.client code, but not for libraries which call user code.

Second, exception chaining reports several lines of additional detail,
which are distracting for experienced users and can be very confusing
for beginners. For example, six of the eleven lines reported for this
simple example relate to exception chaining, and are unnecessary with
BaseException.add_note():

    class Explanation(Exception):
        def __str__(self):
            return "\n" + str(self.args[0])

    try:
        raise AssertionError("Failed!")
    except Exception as e:
        raise Explanation("You can reproduce this error by ...") from e

    $ python example.py
    Traceback (most recent call last):
    File "example.py", line 6, in <module>
        raise AssertionError(why)
    AssertionError: Failed!
                                                        # These lines are
    The above exception was the direct cause of ...     # confusing for new
                                                        # users, and they
    Traceback (most recent call last):                  # only exist due
    File "example.py", line 8, in <module>              # to implementation
        raise Explanation(msg) from e                   # constraints :-(
    Explanation:                                        # Hence this PEP!
    You can reproduce this error by ...

In cases where these two problems do not apply, we encourage use of
exception chaining rather than __notes__.

An assignable __note__ attribute

The first draft and implementation of this PEP defined a single
attribute __note__, which defaulted to None but could have a string
assigned. This is substantially simpler if, and only if, there is at
most one note.

To promote interoperability and support translation of error messages by
libraries such as friendly-traceback, without resorting to dubious
parsing heuristics, we therefore settled on the
.add_note()-and-__notes__ API.

Subclass Exception and add note support downstream

Traceback printing is built into the C code, and reimplemented in pure
Python in traceback.py. To get err.__notes__ printed from a downstream
implementation would also require writing custom traceback-printing
code; while this could be shared between projects and reuse some pieces
of traceback.py[3] we prefer to implement this once, upstream.

Custom exception types could implement their __str__ method to include
our proposed __notes__ semantics, but this would be rarely and
inconsistently applicable.

Don't attach notes to Exceptions, just store them in ExceptionGroups

The initial motivation for this PEP was to associate a note with each
error in an ExceptionGroup. At the cost of a remarkably awkward API and
the cross-referencing problem discussed above, this use-case could be
supported by storing notes on the ExceptionGroup instance instead of on
each exception it contains.

We believe that the cleaner interface, and other use-cases described
above, are sufficient to justify the more general feature proposed by
this PEP.

Add a helper function contextlib.add_exc_note()

It was suggested that we add a utility such as the one below to the
standard library. We do not see this idea as core to the proposal of
this PEP, and thus leave it for later or downstream implementation -
perhaps based on this example code:

    @contextlib.contextmanager
    def add_exc_note(note: str):
        try:
            yield
        except Exception as err:
            err.add_note(note)
            raise

    with add_exc_note(f"While attempting to frobnicate {item=}"):
        frobnicate_or_raise(item)

Augment the raise statement

One discussion proposed raise Exception() with "note contents", but this
does not address the original motivation of compatibility with
ExceptionGroup.

Furthermore, we do not believe that the problem we are solving requires
or justifies new language syntax.

Acknowledgements

We wish to thank the many people who have assisted us through
conversation, code review, design advice, and implementation: Adam
Turner, Alex Grönholm, André Roberge, Barry Warsaw, Brett Cannon, CAM
Gerlach, Carol Willing, Damian, Erlend Aasland, Etienne Pot, Gregory
Smith, Guido van Rossum, Irit Katriel, Jelle Zijlstra, Ken Jin, Kumar
Aditya, Mark Shannon, Matti Picus, Petr Viktorin, Will McGugan, and
pseudonymous commenters on Discord and Reddit.

References

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

[1] this principle was established in the 2003 mail thread which led to
PEP 3134, and included a proposal for a group-of-exceptions type!
https://mail.python.org/pipermail/python-dev/2003-January/032492.html

[2] particularly those at https://bugs.python.org/issue45607,
https://discuss.python.org/t/accepting-pep-654-exception-groups-and-except/10813/9,
https://github.com/python/cpython/pull/28569#discussion_r721768348, and

[3] We note that the exceptiongroup backport package maintains an
exception hook and monkeypatch for TracebackException for Pythons older
than 3.11, and encourage library authors to avoid creating additional
and incompatible backports. We also reiterate our preference for builtin
support which makes such measures unnecessary.


text:
PEP: 760 Title: No More Bare Excepts Author: Pablo Galindo
<EMAIL_ADDRESS>Brett Cannon<EMAIL_ADDRESS>Status:
Withdrawn Type: Standards Track Created: 02-Oct-2024 Python-Version:
3.14 Post-History: 09-Oct-2024

Abstract

This PEP proposes disallowing bare except: clauses in Python's
exception-handling syntax. Currently, Python allows catching all
exceptions with a bare except: clause, which can lead to overly broad
exception handling and mask important errors. This PEP suggests
requiring explicit exception types in all except clauses, promoting more
precise and intentional error handling.

Motivation

The current syntax allows for catching all exceptions with a bare
except: clause:

    try:
        risky_operation()
    except:
        handle_any_error()

While this syntax can be convenient for a "catch all" handler, it often
leads to poor coding practices:

1.  It can mask important errors that should be propagated.
2.  It makes debugging more difficult by catching and potentially hiding
    unexpected exceptions.
3.  It goes against the Python principle of explicit over implicit.

Various linters[1][2][3] and style guides (including PEP 8)[4][5] [6][7]
discourage bare except clauses.

By requiring explicit exception types, we can encourage more thoughtful
and precise error handling:

    try:
        risky_operation()
    except Exception as e:
        handle_expected_error(e)

Another view of this problem is that bare except handlers are ambiguous
regarding the intended handling of terminating exceptions, as the
intention could have been either:

-   Only catch non-terminating exceptions (except Exception:). If this
    was the intention, using a bare except: is an outright bug, since
    that isn't what it means.
-   Catch all exceptions, including terminating ones
    (except BaseException:). using bare except: here it is at least
    correct, but readers need to check to be sure it isn't an instance
    of the first case.

Since both possible intentions have available unambiguous spellings, the
ambiguous form is redundant and that's why we propose to disallow it.

Rationale

The decision to disallow bare except clauses is based on the following
considerations:

1.  Requiring specific exception types makes the programmer's intentions
    clear and encourages thinking about what exceptions might occur.
2.  Catching only specific exceptions makes identifying and debugging
    unexpected errors easier.
3.  Preventing overly broad exception handling reduces the risk of
    silently ignoring critical errors.
4.  Many style guides and linters already discourage the use of bare
    except clauses.

Specification

The syntax for the except clause will be modified to require an
exception type. The grammar will be updated to remove the possibility of
adding an empty expression in except clauses.

This change disallows the bare except: syntax. All except clauses must
specify at least one exception type:

    try:
        ...
    except ValueError:
        ...
    except (TypeError, RuntimeError):
        ...
    except Exception:
        ...  # Still allowed, but catches all exceptions explicitly

The semantics of exception handling remain unchanged, except that it
will no longer be possible to catch all exceptions without explicitly
specifying BaseException or a similarly broad exception type.

Backwards Compatibility

This change is not backwards compatible. Existing code that uses bare
except: clauses will need to be modified. To ease the transition:

1.  A deprecation warning will be issued for bare except clauses in
    Python 3.14.
2.  The syntax will be fully disallowed in Python 3.17.
3.  A from __future__ import strict_excepts will be provided to
    invalidate bare except handlers in earlier versions of Python.

A tool will be provided to automatically update code to replace bare
except: with except BaseException:.

Security Implications

This change has no security implications.

How to Teach This

For new Python users, exception handling should be taught with explicit
exception types from the start:

    try:
        result = risky_operation()
    except ValueError:
        handle_value_error()
    except TypeError:
        handle_type_error()
    except Exception as e:
        handle_unexpected_error(e)

For experienced users, the change can be introduced as a best practice
that is now enforced by the language. The following points should be
emphasized:

1.  Always catch specific exceptions when possible.
2.  Use except Exception: as a last resort for truly unexpected errors.
3.  Never silence exceptions without careful consideration.

Documentation should guide common exception hierarchies and how to
choose appropriate exception types to catch.

Rejected ideas

-   There are genuine cases where the use of bare except: handlers are
    correct. one of the examples that have been raised from Mailman[8]
    involves handling transactions in the face of any exception:

      @contextmanager
          def transaction():
              """Context manager for ensuring the transaction is complete."""
              try:
                  yield
              except:
                  config.db.abort()
                  raise
              else:
                  config.db.commit()

    This code guarantees that no matter what exception occurs, any open
    transaction will be aborted, while in the successful condition, the
    transaction will be committed.

    We do believe that although there are cases such like this one where
    bare except: handlers are correct, it would be better to actually be
    explicit and use except BaseException: for the reasons indicated in
    the "Motivation" section.

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

[1] https://pylint.pycqa.org/en/latest/user_guide/messages/warning/bare-except.html

[2] https://www.flake8rules.com/rules/E722.html

[3] https://docs.astral.sh/ruff/rules/bare-except/

[4] https://google.github.io/styleguide/pyguide.html#24-exceptions

[5] https://chromium.googlesource.com/chromiumos/platform/factory/+/HEAD/CODING_STYLE.md#Avoid-bare_except

[6] https://4.docs.plone.org/develop/plone-coredev/style.html#concrete-rules

[7] https://docs.openedx.org/en/latest/developers/references/developer_guide/style_guides/python-guidelines.html

[8] https://gitlab.com/mailman/mailman/-/blob/master/src/mailman/database/transaction.py#L27

authors:
Brett Cannon
Pablo Galindo