Skip to main content

Python 3.11 changes

·4 mins

In [Packaging] Support Python 3.11 by bebound · Pull Request #26923 · Azure/azure-cli ( , I bumped azure-cli to use Python 3.11. We’ve bump the dependency in other PRs, I thought it should be a small PR, but in the end, a lot of changes are made.

args.getargspec #

getargspec is dropped in 3.11. You can easily replaced it with getfullargspec . It returns FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations) instead of ArgSpec(args, varargs, keywords, defaults) So args, _, kw, _ = inspect.getargspec(fn) can be replaced by args, _, kw, *_ = inspect.getfullargspec(fn) However, getfullargspec is retained primarily for use in code that needs to maintain compatibility with the Python 2 inspect module API.

Note that signature() and Signature Object provide the recommended API for callable introspection, and support additional behaviours (like positional-only arguments) that are sometimes encountered in extension module APIs. This function is retained primarily for use in code that needs to maintain compatibility with the Python 2 inspect module API. –inspect — Inspect live objects — Python 3.11.4 documentation

The modern signature function provides the similar result but needs more modification:

import inspect
def testfunc(a, /, b=1, c=2, *args, kk, **kwargs):


for i, j in inspect.signature(testfunc).parameters.items():
    print(i, type(i), j, type(j), j.kind)

args, _, kw, *_ = inspect.getfullargspec(testfunc)
print(args, kw)

from inspect import Parameter

parameters = inspect.signature(testfunc).parameters
args = [k for k, v in parameters.items() if v.kind in {Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY}]
kw = next(iter([k for k, v in parameters.items() if v.kind == Parameter.VAR_KEYWORD]), None)
print(args, kw)
FullArgSpec(args=['a', 'b', 'c'], varargs='args', varkw='kwargs', defaults=(1, 2), kwonlyargs=['kk'], kwonlydefaults=None, annotations={})
OrderedDict([('a', <Parameter "a">), ('b', <Parameter "b=1">), ('c', <Parameter "c=2">), ('args', <Parameter "*args">), ('kk', <Parameter "kk">), ('kwargs', <Parameter "**kwargs">)])

a <class 'str'> a <class 'inspect.Parameter'> POSITIONAL_ONLY
b <class 'str'> b=1 <class 'inspect.Parameter'> POSITIONAL_OR_KEYWORD
c <class 'str'> c=2 <class 'inspect.Parameter'> POSITIONAL_OR_KEYWORD
args <class 'str'> *args <class 'inspect.Parameter'> VAR_POSITIONAL
kk <class 'str'> kk <class 'inspect.Parameter'> KEYWORD_ONLY
kwargs <class 'str'> **kwargs <class 'inspect.Parameter'> VAR_KEYWORD

['a', 'b', 'c'] kwargs
['a', 'b', 'c'] kwargs

Enum __format__ change #

There is some custom classes in azure-cli, which makes Foo.BAR=‘bar’. In 3.11, the [[][Enum]] =__format__() changes, it returns the enum and member name (ex: Color.RED). (The __str__ method is the same as Python 3.10)

Changed Enum.__format__() (the default for format(), str.format() and f-strings) to always produce the same result as Enum.__str__(): for enums inheriting from ReprEnum it will be the member’s value; for all other enums it will be the enum and member name (e.g. Color.RED). –What’s New In Python 3.11

from enum import Enum
class Foo(str, Enum):
    BAR = "bar"

# Python 3.10
f"{Foo.BAR}"  # > bar
str(Foo.BAR)  # > Foo.BAR

# Python 3.11
f"{Foo.BAR}"  # > Foo.BAR
str(Foo.BAR)  # > Foo.BAR

The standard way to replace Foo class is StrEnum

class Foo(StrEnum):
    BAR = "bar"

# Python 3.11
f"{Foo.BAR}"  # > bar

If you also use Bar(int, Enum), you can replace it with ReprEnum: Bar(int, ReprEnum).

unittest.Mock #

The unittest module replace unittest.mock._importer with pkgutil.resolve_name in bpo-44686 replace unittest.mock._importer with pkgutil.resolve_name by graingert · Pull Request #18544 · python/cpython (, which also introduces some changes.

Previously, it use __import__ to import the patch target, which does not check the module name. But pkgutil.resolve_name will check name first, thus mock.patch fails if the target is not a valid Python module name. For example, this statement fails in 3.11:

@mock.patch('', _mock_network_client_with_existing_vnet_location)

as 2020_09_01_hybrid is not a valid variable name in Python.

            _NAME_PATTERN = re.compile(f'^(?P<pkg>{dotted_words})'

        m = _NAME_PATTERN.match(name)
        if not m:
>           raise ValueError(f'invalid format: {name!r}')
E           ValueError: invalid format: ''

As a workaround, mock.patch.object works.

vnet = import_module('')
with mock.patch.object(vnet, 'List', _mock_network_client_with_existing_vnet):

The ultimate solution is fix module name.

argparse.ArgumentError #

bpo-39716: Raise on conflicting subparser names. by anntzer · Pull Request #18605 · python/cpython ( Raise an ArgumentError when the same subparser name is added twice.

import argparse

parser = argparse.ArgumentParser()
t = parser.add_subparsers()

The above code works on 3.10 but raises this error in 3.11:

Traceback (most recent call last):
  File "C:\Users\kk\Developer\azure-cli\", line 6, in <module>
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1264.0_x64__qbz5n2kfra8p0\Lib\", line 1192, in add_parser
    raise ArgumentError(self, _('conflicting subparser: %s') % name)
argparse.ArgumentError: argument {a}: conflicting subparser: a

Ref #