A tool to automatically upgrade syntax for newer versions.
Project description
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: v2.32.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}
Generator expressions for some built-in functions (pep 289)
-min([i for i in range(3)])
+min(i for i in range(3))
-max([i for i in range(3)])
+max(i for i in range(3))
-sum([i for i in range(3)])
+sum(i for i in range(3))
-''.join([str(i) for i in range(3)])
+''.join(str(i) for i in range(3))
Python2.7+ 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
Availability:
- File imports
from __future__ import unicode_literals --py3-plusis passed on the commandline.
-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'
# `ur` is not a valid string prefix in python3
-u'\d'
+u'\\d'
# this fixes a syntax error in python3.3+
-'\N'
+r'\N'
# note: pyupgrade is timid in one case (that's usually a mistake)
# in python2.x `'\u2603'` is the same as `'\\u2603'` without `unicode_literals`
# but in python3.x, that's our friend ☃
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'
ur string literals
ur'...' literals are not valid in python 3.x
-ur'foo'
+u'foo'
-ur'\s'
+u'\\s'
# unicode escapes are left alone
-ur'\u2603'
+u'\u2603'
-ur'\U0001f643'
+u'\U0001f643'
.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'
Long literals
-5L
+5
-5l
+5
-123456789123456789123456789L
+123456789123456789123456789
Octal literals
-0755
+0o755
-05
+5
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")
unittest deprecated aliases
Rewrites deprecated unittest method aliases to their non-deprecated forms.
Availability:
- More deprecated aliases are rewritten with
--py3-plus
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
Availability:
--py3-plusis passed on the commandline.
class C(Base):
def f(self):
- super(C, self).f()
+ super().f()
"new style" classes
Availability:
--py3-plusis passed on the commandline.
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
Availability:
--py3-plusis passed on the commandline.
-str()
+''
-str("foo")
+"foo"
.encode("utf-8")
Availability:
--py3-plusis passed on the commandline.
-"foo".encode("utf-8")
+"foo".encode()
# coding: ... comment
Availability:
--py3-plusis passed on the commandline.
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 --py3-pluswill also removeabsolute_import/division/print_function/unicode_literals--py37-pluswill also removegenerator_stop
-from __future__ import with_statement
Remove unnecessary py3-compat imports
Availability:
--py3-plusis passed on the commandline.
-from io import open
-from six.moves import map
-from builtins import object # python-future
rewrite mock imports
Availability:
--py3-plusis passed on the commandline.- Unless
--keep-mockis passed on the commandline.
-from mock import patch
+from unittest.mock import patch
yield => yield from
Availability:
--py3-plusis passed on the commandline.
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
Availability:
--py3-plusis passed on the commandline.
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 rewriten as it could introduce a syntax error.
remove six compatibility code
Availability:
--py3-plusis passed on the commandline.
-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
Availability:
--py3-plusis passed on the commandline.
-with io.open('f.txt') as f:
+with open('f.txt') as f:
...
redundant open modes
Availability:
--py3-plusis passed on the commandline.
-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")
OSError aliases
Availability:
--py3-plusis passed on the commandline.
# 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()
typing.Text str alias
Availability:
--py3-plusis passed on the commandline.
-def f(x: Text) -> None:
+def f(x: str) -> None:
...
Unpacking list comprehensions
Availability:
--py3-plusis passed on the commandline.
-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
Availability:
--py3-plusis passed on the commandline.
-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
Availability:
--py3-plusis passed on the commandline.
-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():
...
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():
...
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:
...
remove unnecessary abspath
Availability:
--py39-plusis passed on the commandline.
from os.path import abspath
-abspath(__file__)
+__file__
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:
...
remove quoted annotations
Availability:
- File imports
from __future__ import annotations --py311-plusis passed on the commandline.
-def f(x: 'queue.Queue[int]') -> C:
+def f(x: queue.Queue[int]) -> C:
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyupgrade-2.32.0.tar.gz.
File metadata
- Download URL: pyupgrade-2.32.0.tar.gz
- Upload date:
- Size: 40.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.9 tqdm/4.63.0 importlib-metadata/4.11.3 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6878116d364b72f0c0011dd62dfe96425041a5f753da298b6eacde0f9fd9c004
|
|
| MD5 |
5403581200a509e05d0e701f590f3bfa
|
|
| BLAKE2b-256 |
ce9ecbd32175d7fa5c78f4e6650aa1ebdef244ce6c51a80174fa86feaf913c29
|
File details
Details for the file pyupgrade-2.32.0-py2.py3-none-any.whl.
File metadata
- Download URL: pyupgrade-2.32.0-py2.py3-none-any.whl
- Upload date:
- Size: 55.8 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.9 tqdm/4.63.0 importlib-metadata/4.11.3 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f45d4afb6ccdf7b0cea757958d0a11306324052668d9ff99d2bcb06bda46c00d
|
|
| MD5 |
a919f87c6df3b6905bbb400943de91a2
|
|
| BLAKE2b-256 |
17816d91f15154e1bc31c8592ed7c357e05315dbd3b17bc304b2e7dbca263288
|