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-formatis 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-pluswill also removegenerator_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-pluswill remove Python <= 3.5 only blocks--py37-pluswill 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.
-@pytest.mark.skipif(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
-@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-plusforsocket.timeout--py311-plusforasyncio.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-plusis 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-plusis 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-plusis 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-plusis 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-plusis passed on the commandline.
import functools
-@functools.lru_cache()
+@functools.lru_cache
def expensive():
...
shlex.join
Availability:
--py38-plusis 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-plusis passed on the commandline.
import functools
-@functools.lru_cache(maxsize=None)
+@functools.cache
def expensive():
...
removeprefix / removesuffix
Availability:
--py39-plusis 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-typingis passed on the commandline.
- Unless
--py39-plusis 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-typingis passed on the commandline.
- Unless
--py310-plusis 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-typingis passed on the commandline.
- Unless
--py313-plusis 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-plusis passed on the commandline.
import datetime
-datetime.timezone.utc
+datetime.UTC
sentinel
Availability:
--py315-plusis passed on the commandline.
-_MISSING = object()
+_MISSING = sentinel('_MISSING')
utf-8 mode for open
Availability:
--py315-plusis passed on the commandline.
-with open(fname, encoding="utf-8") as f:
+with open(fname) as f:
comprehension unpackings
Availability:
--py315-plusis 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-plusis 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-plusis passed on the commandline.
-if TYPE_CHECKING:
- from typing import Self
+lazy from typing import Self
remove ArgumentParser(suggest_on_error=True)
Availability:
--py315-plusis passed on the commandline.
-parser = argparse.ArgumentParser(suggest_on_error=True)
+parser = argparse.ArgumentParser()
Metadata
Release files for pyupgrade 3.22.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pyupgrade-3.22.0.tar.gz | 49.9 kB | Without provenance | Details for pyupgrade-3.22.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pyupgrade-3.22.0-py2.py3-none-any.whl | Python 3, Python 2 | none | any | Details for pyupgrade-3.22.0-py2.py3-none-any.whl |
Total release size: 121.3 kB
Release files / pyupgrade-3.22.0.tar.gz
| Download URL | pyupgrade-3.22.0.tar.gz |
|---|---|
| Size | 49.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6889a7be7f0d0ea3119789bc50086a008a91ed6c46005a030f8928b501550425
|
|
BLAKE2b-256 checksum How to use checksums |
4a3b729d6a29209ff0d16726f3623177c7cfb23f190d3c25bb61e651dd55d371
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / pyupgrade-3.22.0-py2.py3-none-any.whl
| Download URL | pyupgrade-3.22.0-py2.py3-none-any.whl |
|---|---|
| Size | 71.4 kB |
| Tags | Python 2 Python 3 |
|
SHA-256 checksum How to use checksums |
98c87952345cca4dec6589436b9c1e4b2e8c09617f41aa1582fb448728201024
|
|
BLAKE2b-256 checksum How to use checksums |
47a3222eacd24aac6e87b337c5f3961cb120e6b79233b6798248fb1af362f613
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Security for pyupgrade 3.22.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.