Skip to main content

A tool to automatically upgrade syntax for newer versions.

Project description

build status status


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


pip install pyupgrade

As a pre-commit hook

See pre-commit for instructions

Sample .pre-commit-config.yaml:

-   repo:
    rev: v3.15.2
    -   id: pyupgrade

Implemented features

Set literals

-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(lambda: list())
-defaultdict(lambda: {})
-defaultdict(lambda: dict())
-defaultdict(lambda: ())
-defaultdict(lambda: tuple())
-defaultdict(lambda: set())
-defaultdict(lambda: 0)
-defaultdict(lambda: 0.0)
-defaultdict(lambda: 0j)
-defaultdict(lambda: '')

Format Specifiers

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

printf-style string formatting


  • 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


Invalid escape sequences

 # strings with only invalid sequences become raw strings
 # strings with mixed valid / invalid sequences get escaped
 # this fixes a syntax error in python3.3+

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


extraneous parens in print(...)

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

 # ok: printing an empty tuple
 # ok: printing a tuple
 # ok: parenthesized generator argument
 sum((i for i in range(3)), [])
 # fixed:

constant fold isinstance / issubclass / except

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

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

-except (Error1, Error1, Error2):
+except (Error1, Error2):

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




# coding: ... comment

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

-# coding: utf-8
 x = 1

__future__ import removal


  • 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


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

see also reorder-python-imports

some examples:

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

rewrite mock imports


-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')
-    print('py3')


  • --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')
-    print('py3.6+')

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

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

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

remove six compatibility code

-six.exec_(c, g, l)
+exec(c, g, l)

-from six import text_type

 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

-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.indexbytes(bs, i)
-six.create_unbound_method(fn, cls)
-six.raise_from(exc, exc_from)
+raise exc from exc_from
-six.reraise(tp, exc, tb)
+raise exc.with_traceback(tb)
-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*

open alias

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

redundant open modes

-open("foo", "U")
-open("foo", "Ur")
-open("foo", "Ub")
+open("foo", "rb")
-open("foo", "rUb")
+open("foo", "rb")
-open("foo", "r")
-open("foo", "rt")
-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():
-    except EnvironmentError:
+    except OSError:

TimeoutError aliases


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

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

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


typing.NamedTuple / typing.TypedDict py36+ syntax


  • --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



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

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). replace universal_newlines with text


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


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

remove parentheses from @functools.lru_cache()


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

 def expensive():



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

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


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

 def expensive():

pep 585 typing rewrites


  • 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


  • 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:

remove quoted annotations


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

use datetime.UTC alias


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


Project details

Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyupgrade-3.15.2.tar.gz (45.0 kB view hashes)

Uploaded Source

Built Distribution

pyupgrade-3.15.2-py2.py3-none-any.whl (61.2 kB view hashes)

Uploaded Python 2 Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page