pyupgrade 3.21.0


pip install pyupgrade

  Latest version

Released: Oct 09, 2025

Project Links

Meta
Author: Anthony Sottile
Requires Python: >=3.9

Classifiers

Programming Language
  • Python :: 3
  • Python :: 3 :: Only
  • Python :: Implementation :: CPython
  • Python :: Implementation :: PyPy

build status pre-commit.ci status

pyupgrade

A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language.

Installation

pip install pyupgrade

As a pre-commit hook

See pre-commit for instructions

Sample .pre-commit-config.yaml:

-   repo: https://github.com/asottile/pyupgrade
    rev: v3.21.0
    hooks:
    -   id: pyupgrade

Implemented features

Set literals

-set(())
+set()
-set([])
+set()
-set((1,))
+{1}
-set((1, 2))
+{1, 2}
-set([1, 2])
+{1, 2}
-set(x for x in y)
+{x for x in y}
-set([x for x in y])
+{x for x in y}

Dictionary comprehensions

-dict((a, b) for a, b in y)
+{a: b for a, b in y}
-dict([(a, b) for a, b in y])
+{a: b for a, b in y}

Replace unnecessary lambdas in collections.defaultdict calls

-defaultdict(lambda: [])
+defaultdict(list)
-defaultdict(lambda: list())
+defaultdict(list)
-defaultdict(lambda: {})
+defaultdict(dict)
-defaultdict(lambda: dict())
+defaultdict(dict)
-defaultdict(lambda: ())
+defaultdict(tuple)
-defaultdict(lambda: tuple())
+defaultdict(tuple)
-defaultdict(lambda: set())
+defaultdict(set)
-defaultdict(lambda: 0)
+defaultdict(int)
-defaultdict(lambda: 0.0)
+defaultdict(float)
-defaultdict(lambda: 0j)
+defaultdict(complex)
-defaultdict(lambda: '')
+defaultdict(str)

Format Specifiers

-'{0} {1}'.format(1, 2)
+'{} {}'.format(1, 2)
-'{0}' '{1}'.format(1, 2)
+'{}' '{}'.format(1, 2)

printf-style string formatting

Availability:

  • Unless --keep-percent-format is passed.
-'%s %s' % (a, b)
+'{} {}'.format(a, b)
-'%r %2f' % (a, b)
+'{!r} {:2f}'.format(a, b)
-'%(a)s %(b)s' % {'a': 1, 'b': 2}
+'{a} {b}'.format(a=1, b=2)

Unicode literals

-u'foo'
+'foo'
-u"foo"
+'foo'
-u'''foo'''
+'''foo'''

Invalid escape sequences

 # strings with only invalid sequences become raw strings
-'\d'
+r'\d'
 # strings with mixed valid / invalid sequences get escaped
-'\n\d'
+'\n\\d'
-u'\d'
+r'\d'
 # this fixes a syntax error in python3.3+
-'\N'
+r'\N'

is / is not comparison to constant literals

In python3.8+, comparison to literals becomes a SyntaxWarning as the success of those comparisons is implementation specific (due to common object caching).

-x is 5
+x == 5
-x is not 5
+x != 5
-x is 'foo'
+x == 'foo'

.encode() to bytes literals

-'foo'.encode()
+b'foo'
-'foo'.encode('ascii')
+b'foo'
-'foo'.encode('utf-8')
+b'foo'
-u'foo'.encode()
+b'foo'
-'\xa0'.encode('latin1')
+b'\xa0'

extraneous parens in print(...)

A fix for python-modernize/python-modernize#178

 # ok: printing an empty tuple
 print(())
 # ok: printing a tuple
 print((1,))
 # ok: parenthesized generator argument
 sum((i for i in range(3)), [])
 # fixed:
-print(("foo"))
+print("foo")

constant fold isinstance / issubclass / except

-isinstance(x, (int, int))
+isinstance(x, int)

-issubclass(y, (str, str))
+issubclass(y, str)

 try:
     raises()
-except (Error1, Error1, Error2):
+except (Error1, Error2):
     pass

unittest deprecated aliases

Rewrites deprecated unittest method aliases to their non-deprecated forms.

 from unittest import TestCase


 class MyTests(TestCase):
     def test_something(self):
-        self.failUnlessEqual(1, 1)
+        self.assertEqual(1, 1)
-        self.assertEquals(1, 1)
+        self.assertEqual(1, 1)

super() calls

 class C(Base):
     def f(self):
-        super(C, self).f()
+        super().f()

"new style" classes

rewrites class declaration

-class C(object): pass
+class C: pass
-class C(B, object): pass
+class C(B): pass

removes __metaclass__ = type declaration

 class C:
-    __metaclass__ = type

forced str("native") literals

-str()
+''
-str("foo")
+"foo"

.encode("utf-8")

-"foo".encode("utf-8")
+"foo".encode()

# coding: ... comment

as of PEP 3120, the default encoding for python source is UTF-8

-# coding: utf-8
 x = 1

__future__ import removal

Availability:

  • by default removes nested_scopes, generators, with_statement, absolute_import, division, print_function, unicode_literals
  • --py37-plus will also remove generator_stop
-from __future__ import with_statement

Remove unnecessary py3-compat imports

-from io import open
-from six.moves import map
-from builtins import object  # python-future

import replacements

Availability:

  • --py36-plus (and others) will replace imports

see also reorder-python-imports

some examples:

-from collections import deque, Mapping
+from collections import deque
+from collections.abc import Mapping
-from typing import Sequence
+from collections.abc import Sequence
-from typing_extensions import Concatenate
+from typing import Concatenate

rewrite mock imports

Availability:

-from mock import patch
+from unittest.mock import patch

yield => yield from

 def f():
-    for x in y:
-        yield x
+    yield from y
-    for a, b in c:
-        yield (a, b)
+    yield from c

Python2 and old Python3.x blocks

 import sys
-if sys.version_info < (3,):  # also understands `six.PY2` (and `not`), `six.PY3` (and `not`)
-    print('py2')
-else:
-    print('py3')
+print('py3')

Availability:

  • --py36-plus will remove Python <= 3.5 only blocks
  • --py37-plus will remove Python <= 3.6 only blocks
  • so on and so forth
 # using --py36-plus for this example

 import sys
-if sys.version_info < (3, 6):
-    print('py3.5')
-else:
-    print('py3.6+')
+print('py3.6+')

-if sys.version_info <= (3, 5):
-    print('py3.5')
-else:
-    print('py3.6+')
+print('py3.6+')

-if sys.version_info >= (3, 6):
-    print('py3.6+')
-else:
-    print('py3.5')
+print('py3.6+')

Note that if blocks without an else will not be rewritten as it could introduce a syntax error.

remove six compatibility code

-six.text_type
+str
-six.binary_type
+bytes
-six.class_types
+(type,)
-six.string_types
+(str,)
-six.integer_types
+(int,)
-six.unichr
+chr
-six.iterbytes
+iter
-six.print_(...)
+print(...)
-six.exec_(c, g, l)
+exec(c, g, l)
-six.advance_iterator(it)
+next(it)
-six.next(it)
+next(it)
-six.callable(x)
+callable(x)
-six.moves.range(x)
+range(x)
-six.moves.xrange(x)
+range(x)


-from six import text_type
-text_type
+str

-@six.python_2_unicode_compatible
 class C:
     def __str__(self):
         return u'C()'

-class C(six.Iterator): pass
+class C: pass

-class C(six.with_metaclass(M, B)): pass
+class C(B, metaclass=M): pass

-@six.add_metaclass(M)
-class C(B): pass
+class C(B, metaclass=M): pass

-isinstance(..., six.class_types)
+isinstance(..., type)
-issubclass(..., six.integer_types)
+issubclass(..., int)
-isinstance(..., six.string_types)
+isinstance(..., str)

-six.b('...')
+b'...'
-six.u('...')
+'...'
-six.byte2int(bs)
+bs[0]
-six.indexbytes(bs, i)
+bs[i]
-six.int2byte(i)
+bytes((i,))
-six.iteritems(dct)
+dct.items()
-six.iterkeys(dct)
+dct.keys()
-six.itervalues(dct)
+dct.values()
-next(six.iteritems(dct))
+next(iter(dct.items()))
-next(six.iterkeys(dct))
+next(iter(dct.keys()))
-next(six.itervalues(dct))
+next(iter(dct.values()))
-six.viewitems(dct)
+dct.items()
-six.viewkeys(dct)
+dct.keys()
-six.viewvalues(dct)
+dct.values()
-six.create_unbound_method(fn, cls)
+fn
-six.get_unbound_function(meth)
+meth
-six.get_method_function(meth)
+meth.__func__
-six.get_method_self(meth)
+meth.__self__
-six.get_function_closure(fn)
+fn.__closure__
-six.get_function_code(fn)
+fn.__code__
-six.get_function_defaults(fn)
+fn.__defaults__
-six.get_function_globals(fn)
+fn.__globals__
-six.raise_from(exc, exc_from)
+raise exc from exc_from
-six.reraise(tp, exc, tb)
+raise exc.with_traceback(tb)
-six.reraise(*sys.exc_info())
+raise
-six.assertCountEqual(self, a1, a2)
+self.assertCountEqual(a1, a2)
-six.assertRaisesRegex(self, e, r, fn)
+self.assertRaisesRegex(e, r, fn)
-six.assertRegex(self, s, r)
+self.assertRegex(s, r)

 # note: only for *literals*
-six.ensure_binary('...')
+b'...'
-six.ensure_str('...')
+'...'
-six.ensure_text('...')
+'...'

open alias

-with io.open('f.txt') as f:
+with open('f.txt') as f:
     ...

redundant open modes

-open("foo", "U")
+open("foo")
-open("foo", "Ur")
+open("foo")
-open("foo", "Ub")
+open("foo", "rb")
-open("foo", "rUb")
+open("foo", "rb")
-open("foo", "r")
+open("foo")
-open("foo", "rt")
+open("foo")
-open("f", "r", encoding="UTF-8")
+open("f", encoding="UTF-8")
-open("f", "wt")
+open("f", "w")

OSError aliases

 # also understands:
 # - IOError
 # - WindowsError
 # - mmap.error and uses of `from mmap import error`
 # - select.error and uses of `from select import error`
 # - socket.error and uses of `from socket import error`

 def throw():
-    raise EnvironmentError('boom')
+    raise OSError('boom')

 def catch():
     try:
         throw()
-    except EnvironmentError:
+    except OSError:
         handle_error()

TimeoutError aliases

Availability:

  • --py310-plus for socket.timeout
  • --py311-plus for asyncio.TimeoutError
 def throw(a):
     if a:
-        raise asyncio.TimeoutError('boom')
+        raise TimeoutError('boom')
     else:
-        raise socket.timeout('boom')
+        raise TimeoutError('boom')

 def catch(a):
     try:
         throw(a)
-    except (asyncio.TimeoutError, socket.timeout):
+    except TimeoutError:
         handle_error()

typing.Text str alias

-def f(x: Text) -> None:
+def f(x: str) -> None:
     ...

Unpacking list comprehensions

-foo, bar, baz = [fn(x) for x in items]
+foo, bar, baz = (fn(x) for x in items)

Rewrite xml.etree.cElementTree to xml.etree.ElementTree

-import xml.etree.cElementTree as ET
+import xml.etree.ElementTree as ET
-from xml.etree.cElementTree import XML
+from xml.etree.ElementTree import XML

Rewrite type of primitive

-type('')
+str
-type(b'')
+bytes
-type(0)
+int
-type(0.)
+float

typing.NamedTuple / typing.TypedDict py36+ syntax

Availability:

  • --py36-plus is passed on the commandline.
-NT = typing.NamedTuple('NT', [('a', int), ('b', Tuple[str, ...])])
+class NT(typing.NamedTuple):
+    a: int
+    b: Tuple[str, ...]

-D1 = typing.TypedDict('D1', a=int, b=str)
+class D1(typing.TypedDict):
+    a: int
+    b: str

-D2 = typing.TypedDict('D2', {'a': int, 'b': str})
+class D2(typing.TypedDict):
+    a: int
+    b: str

f-strings

Availability:

  • --py36-plus is passed on the commandline.
-'{foo} {bar}'.format(foo=foo, bar=bar)
+f'{foo} {bar}'
-'{} {}'.format(foo, bar)
+f'{foo} {bar}'
-'{} {}'.format(foo.bar, baz.womp)
+f'{foo.bar} {baz.womp}'
-'{} {}'.format(f(), g())
+f'{f()} {g()}'
-'{x}'.format(**locals())
+f'{x}'

note: pyupgrade is intentionally timid and will not create an f-string if it would make the expression longer or if the substitution parameters are sufficiently complicated (as this can decrease readability).

subprocess.run: replace universal_newlines with text

Availability:

  • --py37-plus is passed on the commandline.
-output = subprocess.run(['foo'], universal_newlines=True)
+output = subprocess.run(['foo'], text=True)

subprocess.run: replace stdout=subprocess.PIPE, stderr=subprocess.PIPE with capture_output=True

Availability:

  • --py37-plus is passed on the commandline.
-output = subprocess.run(['foo'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+output = subprocess.run(['foo'], capture_output=True)

remove parentheses from @functools.lru_cache()

Availability:

  • --py38-plus is passed on the commandline.
 import functools

-@functools.lru_cache()
+@functools.lru_cache
 def expensive():
     ...

shlex.join

Availability:

  • --py38-plus is passed on the commandline.
-' '.join(shlex.quote(arg) for arg in cmd)
+shlex.join(cmd)

replace @functools.lru_cache(maxsize=None) with shorthand

Availability:

  • --py39-plus is passed on the commandline.
 import functools

-@functools.lru_cache(maxsize=None)
+@functools.cache
 def expensive():
     ...

pep 585 typing rewrites

Availability:

  • File imports from __future__ import annotations
    • Unless --keep-runtime-typing is passed on the commandline.
  • --py39-plus is passed on the commandline.
-def f(x: List[str]) -> None:
+def f(x: list[str]) -> None:
     ...

pep 604 typing rewrites

Availability:

  • File imports from __future__ import annotations
    • Unless --keep-runtime-typing is passed on the commandline.
  • --py310-plus is passed on the commandline.
-def f() -> Optional[str]:
+def f() -> str | None:
     ...
-def f() -> Union[int, str]:
+def f() -> int | str:
     ...

pep 696 TypeVar defaults

Availability:

  • File imports from __future__ import annotations
    • Unless --keep-runtime-typing is passed on the commandline.
  • --py313-plus is passed on the commandline.
-def f() -> Generator[int, None, None]:
+def f() -> Generator[int]:
     yield 1
-async def f() -> AsyncGenerator[int, None]:
+async def f() -> AsyncGenerator[int]:
     yield 1

remove quoted annotations

Availability:

  • File imports from __future__ import annotations
-def f(x: 'queue.Queue[int]') -> C:
+def f(x: queue.Queue[int]) -> C:

use datetime.UTC alias

Availability:

  • --py311-plus is passed on the commandline.
 import datetime

-datetime.timezone.utc
+datetime.UTC
3.21.0 Oct 09, 2025
3.20.0 May 23, 2025
3.19.1 Dec 17, 2024
3.19.0 Oct 22, 2024
3.18.0 Oct 11, 2024
3.17.0 Jul 28, 2024
3.16.0 Jun 08, 2024
3.15.2 Mar 24, 2024
3.15.1 Feb 18, 2024
3.15.0 Oct 07, 2023
3.14.0 Oct 02, 2023
3.13.0 Sep 23, 2023
3.12.0 Sep 21, 2023
3.11.2 Sep 21, 2023
3.11.1 Sep 19, 2023
3.11.0 Sep 15, 2023
3.10.1 Jul 30, 2023
3.10.0 Jul 30, 2023
3.9.0 Jul 08, 2023
3.8.0 Jul 01, 2023
3.7.0 Jun 18, 2023
3.6.0 Jun 10, 2023
3.5.0 Jun 10, 2023
3.4.1 Jun 10, 2023
3.4.0 May 06, 2023
3.3.2 Apr 25, 2023
3.3.1 Dec 06, 2022
3.3.0 Dec 02, 2022
3.2.3 Nov 29, 2022
3.2.2 Nov 10, 2022
3.2.1 Nov 10, 2022
3.2.0 Oct 29, 2022
3.1.0 Oct 10, 2022
3.0.0 Oct 04, 2022
2.38.4 Oct 03, 2022
2.38.3 Oct 03, 2022
2.38.2 Sep 26, 2022
2.38.1 Sep 24, 2022
2.38.0 Sep 15, 2022
2.37.3 Jul 26, 2022
2.37.2 Jul 19, 2022
2.37.1 Jul 11, 2022
2.37.0 Jul 10, 2022
2.36.0 Jul 10, 2022
2.35.0 Jul 10, 2022
2.34.0 Jun 06, 2022
2.33.0 Jun 06, 2022
2.32.1 May 05, 2022
2.32.0 Apr 08, 2022
2.31.1 Mar 13, 2022
2.31.0 Jan 01, 2022
2.30.1 Dec 30, 2021
2.30.0 Dec 28, 2021
2.29.1 Nov 16, 2021
2.29.0 Sep 28, 2021
2.28.1 Sep 27, 2021
2.28.0 Sep 25, 2021
2.27.0 Sep 23, 2021
2.26.0.post1 Sep 17, 2021
2.26.0 Sep 12, 2021
2.25.1 Sep 10, 2021
2.25.0 Aug 28, 2021
2.24.0 Aug 19, 2021
2.23.3 Aug 04, 2021
2.23.2 Aug 02, 2021
2.23.1 Jul 29, 2021
2.23.0 Jul 25, 2021
2.22.0 Jul 24, 2021
2.21.2 Jul 16, 2021
2.21.1 Jul 15, 2021
2.21.0 Jul 10, 2021
2.20.1 Jul 10, 2021
2.20.0 Jul 03, 2021
2.19.4 Jun 11, 2021
2.19.3 Jun 09, 2021
2.19.2 Jun 09, 2021
2.19.1 Jun 01, 2021
2.19.0 May 29, 2021
2.18.3 May 25, 2021
2.18.2 May 24, 2021
2.18.1 May 21, 2021
2.18.0 May 21, 2021
2.17.0 May 18, 2021
2.16.0 May 15, 2021
2.15.0 May 08, 2021
2.14.0 May 01, 2021
2.13.0 Apr 20, 2021
2.12.0 Apr 09, 2021
2.11.0 Mar 20, 2021
2.10.1 Mar 16, 2021
2.10.0 Feb 08, 2021
2.9.0 Feb 01, 2021
2.8.0 Jan 31, 2021
2.7.4 Nov 11, 2020
2.7.3 Oct 22, 2020
2.7.2 Jul 24, 2020
2.7.1 Jul 16, 2020
2.7.0 Jul 09, 2020
2.6.2 Jun 25, 2020
2.6.1 Jun 12, 2020
2.6.0 Jun 12, 2020
2.5.0 Jun 07, 2020
2.4.4 May 24, 2020
2.4.3 May 17, 2020
2.4.2 May 16, 2020
2.4.1 May 05, 2020
2.4.0 May 04, 2020
2.3.0 Apr 25, 2020
2.2.1 Apr 21, 2020
2.2.0 Apr 16, 2020
2.1.1 Apr 12, 2020
2.1.0 Feb 24, 2020
2.0.2 Feb 24, 2020
2.0.1 Feb 20, 2020
2.0.0 Feb 20, 2020
1.27.0 Feb 20, 2020
1.26.2 Jan 22, 2020
1.26.1 Jan 18, 2020
1.26.0 Jan 12, 2020
1.25.3 Jan 08, 2020
1.25.2 Dec 05, 2019
1.25.1 Oct 20, 2019
1.25.0 Oct 17, 2019
1.24.1 Oct 07, 2019
1.24.0 Sep 23, 2019
1.23.0 Aug 28, 2019
1.22.1 Aug 07, 2019
1.22.0 Aug 02, 2019
1.21.0 Jul 09, 2019
1.20.2 Jul 09, 2019
1.20.1 Jul 06, 2019
1.20.0 Jul 05, 2019
1.19.0 Jun 16, 2019
1.18.0 Jun 02, 2019
1.17.1 May 17, 2019
1.17.0 May 13, 2019
1.16.3 Apr 25, 2019
1.16.2 Apr 25, 2019
1.16.1 Apr 22, 2019
1.16.0 Apr 15, 2019
1.15.0 Apr 08, 2019
1.14.0 Mar 31, 2019
1.13.0 Mar 17, 2019
1.12.0 Feb 28, 2019
1.11.3 Feb 17, 2019
1.11.2 Feb 07, 2019
1.11.1 Jan 20, 2019
1.11.0 Nov 25, 2018
1.10.1 Oct 31, 2018
1.10.0 Oct 28, 2018
1.9.1 Oct 23, 2018
1.9.0 Oct 21, 2018
1.8.0 Oct 08, 2018
1.7.0 Oct 08, 2018
1.6.1 Sep 17, 2018
1.6.0 Sep 10, 2018
1.5.1 Sep 06, 2018
1.5.0 Sep 03, 2018
1.4.0 Jun 23, 2018
1.3.1 Jun 08, 2018
1.3.0 Jun 06, 2018
1.2.0 Aug 20, 2017
1.1.4 Jul 28, 2017
1.1.3 Jul 14, 2017
1.1.2 Jul 02, 2017
1.1.1 Jun 02, 2017
1.1.0 May 13, 2017
1.0.6 Mar 23, 2017
1.0.5 Mar 20, 2017
1.0.4 Mar 15, 2017
1.0.3 Mar 14, 2017
1.0.2 Mar 13, 2017
1.0.1 Mar 13, 2017
1.0.0 Mar 13, 2017
0.0.0 Mar 13, 2017

Wheel compatibility matrix

Platform Python 2 Python 3
any

Files in release

Extras: None
Dependencies:
tokenize-rt (>=6.1.0)