Profile
Back to NewsBack
GitHub Trending 4 min
Reader Mode
asottile/pyupgrade: A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language.

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

13 hours ago

build status</a> pre-commit.ci status</a>

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.22.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")

[python-modernize/python-modernize#178]: https://github.com/python-modernize/python-modernize/issues/178

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

[PEP 3120]: https://www.python.org/dev/peps/pep-3120/

__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.

versioned @pytest.mark.skipif decorators

similar to above: redundant skipif marks are removed.

[email protected](sys.version_info < (3, 6), reason="py36+")
 def test_func():
     ...

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

[email protected]_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

[email protected]_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

[email protected]_cache() [email protected]_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

[email protected]_cache(maxsize=None) [email protected] def expensive(): ...

removeprefix / removesuffix

Availability:

  • --py39-plus is passed on the commandline.
-if x.startswith(y):
  • x = x[len(y):]
+x = x.removeprefix(y)

-if x.endswith(y):

  • x = x[:-1 * len(y)]
+x = x.removesuffix(y)

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

sentinel

Availability:

  • --py315-plus is passed on the commandline.
-_MISSING = object()
+_MISSING = sentinel('_MISSING')

utf-8 mode for open

Availability:

  • --py315-plus is passed on the commandline.
-with open(fname, encoding="utf-8") as f:
+with open(fname) as f:

comprehension unpackings

Availability:

  • --py315-plus is passed on the commandline.
-listcomp = [x for a in b for x in a]
+listcomp = [*a for a in b]
-setcomp = {x for a in b for x in a}
+setcomp = {*a for a in b}
-genexpr = (x for a in b for x in a)
+genexpr = (*a for a in b)
-dictcomp = {k: v for a in b for k, v in a.items()}
+dictcomp = {**a for a in b}

note: pyupgrade will rewrite non-nested redundant comprehensions independent of version:

-listcomp = [x for x in y]
+listcomp = [*y]

itertools.chain

Availability:

  • --py315-plus is passed on the commandline.
-itertools.chain.from_iterable(x)
+(*it for it in x)
-itertools.chain(x)
+iter(x)
-itertools.chain(*x)
+(*it for it in x)
-itertools.chain(a, b, c)
+(*it for it in (a, b, c))

lazy-ify TYPE_CHECKING imports

Availability:

  • --py315-plus is passed on the commandline.
-if TYPE_CHECKING:
  • from typing import Self
+lazy from typing import Self

remove ArgumentParser(suggest_on_error=True)

Availability:

  • --py315-plus is passed on the commandline.
-parser = argparse.ArgumentParser(suggest_on_error=True)
+parser = argparse.ArgumentParser()

Chat with me