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.10.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}
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-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
Availability:
- File imports
from __future__ import unicode_literals
--py3-plus
is 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
print(...)
extraneous parens in A fix for python-modernize/python-modernize#178
print(()) # ok: printing an empty tuple
print((1,)) # ok: printing a tuple
sum((i for i in range(3)), []) # ok: parenthesized generator argument
print(("foo")) # print("foo")
super()
calls
Availability:
--py3-plus
is passed on the commandline.
class C(Base):
def f(self):
super(C, self).f() # super().f()
"new style" classes
Availability:
--py3-plus
is passed on the commandline.
rewrites class declaration
class C(object): pass # class C: pass
class C(B, object): pass # class C(B): pass
__metaclass__ = type
declaration
removes -__metaclass__ = type
str("native")
literals
forced Availability:
--py3-plus
is passed on the commandline.
str() # "''"
str("foo") # "foo"
.encode("utf-8")
Availability:
--py3-plus
is passed on the commandline.
"foo".encode("utf-8") # "foo".encode()
# coding: ...
comment
Availability:
--py3-plus
is 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-plus
will also removeabsolute_import
/division
/print_function
/unicode_literals
--py37-plus
will also removegenerator_stop
-from __future__ import with_statement
Remove unnecessary py3-compat imports
Availability:
--py3-plus
is passed on the commandline.
-from io import open
-from six.moves import map
-from builtins import object # python-future
mock
imports
rewrite Availability:
--py3-plus
is passed on the commandline.- Unless
--keep-mock
is passed on the commandline.
-from mock import patch
+from unittest.mock import patch
yield
=> yield from
Availability:
--py3-plus
is passed on the commandline.
def f():
for x in y: # yield from y
yield x
for a, b in c: # yield from c
yield (a, b)
if PY2
blocks
Availability:
--py3-plus
is passed on the commandline.
# input
if six.PY2: # also understands `six.PY3` and `not` and `sys.version_info`
print('py2')
else:
print('py3')
# output
print('py3')
six
compatibility code
remove Availability:
--py3-plus
is 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)
from six import text_type
text_type # str
@six.python_2_unicode_compatible # decorator is removed
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, metaclass=M): pass
class C(B): 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-plus
is passed on the commandline.
-with io.open('f.txt') as f:
+with open('f.txt') as f:
...
open
modes
redundant Availability:
--py3-plus
is 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-plus
is passed on the commandline.
# input
# 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`
try:
raise EnvironmentError('boom')
except EnvironmentError:
raise
# output
try:
raise OSError('boom')
except OSError:
raise
typing.Text
str alias
Availability:
--py3-plus
is passed on the commandline.
-def f(x: Text) -> None:
+def f(x: str) -> None:
...
typing.NamedTuple
/ typing.TypedDict
py36+ syntax
Availability:
--py36-plus
is passed on the commandline.
# input
NT = typing.NamedTuple('NT', [('a', int), ('b', Tuple[str, ...])])
D1 = typing.TypedDict('D1', a=int, b=str)
D2 = typing.TypedDict('D2', {'a': int, 'b': str})
# output
class NT(typing.NamedTuple):
a: int
b: Tuple[str, ...]
class D1(typing.TypedDict):
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()}'
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 anything but simple names or dotted names (as this can decrease readability).
@functools.lru_cache()
remove parentheses from Availability:
--py38-plus
is passed on the commandline.
import functools
-@functools.lru_cache()
+@functools.lru_cache
def expensive():
...
@functools.lru_cache(maxsize=None)
with shorthand
replace 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.
- Unless
--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.
- Unless
--py310-plus
is passed on the commandline.
-def f() -> Optional[str]:
+def f() -> str | None:
...
-def f() -> Union[int, str]:
+def f() -> int | str:
...