Project Icon

pyupgrade

智能更新Python代码语法的开源工具

pyupgrade是一个开源的Python代码语法升级工具,可作为独立程序或pre-commit钩子使用。它能自动将旧版Python语法转换为新版特性,支持集合字面量、字典推导式、f-strings等多种语法优化。该工具有助于开发者保持代码的现代化和一致性,提高Python项目的质量。

构建状态 pre-commit.ci 状态

pyupgrade

一个用于自动升级较新版本语言语法的工具(和 pre-commit 钩子)。

安装

pip install pyupgrade

作为 pre-commit 钩子使用

使用说明请参见 pre-commit

示例 .pre-commit-config.yaml

-   repo: https://github.com/asottile/pyupgrade
    rev: v3.17.0
    hooks:
    -   id: pyupgrade

已实现的功能

集合字面量

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

字典推导式

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

替换 collections.defaultdict 调用中不必要的 lambda 函数

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

格式说明符

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

printf 风格的字符串格式化

适用性:

  • 除非传递了 --keep-percent-format 参数。
-'%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 字面量

-u'foo'
+'foo'
-u"foo"
+'foo'
-u'''foo'''
+'''foo'''

无效的转义序列

 # 只包含无效序列的字符串变为原始字符串
-'\d'
+r'\d'
 # 混合有效和无效序列的字符串会被转义
-'\n\d'
+'\n\\d'
-u'\d'
+r'\d'
 # 这修复了 python3.3+ 中的语法错误
-'\N'
+r'\N'

与常量字面量进行 is / is not 比较

在 python3.8+ 中,与字面量的比较会产生 SyntaxWarning,因为这些比较的成功与否取决于具体实现(由于常见对象缓存)。

-x is 5
+x == 5
-x is not 5
+x != 5
-x is 'foo'
+x == 'foo'

.encode() 到字节字面量

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

print(...) 中多余的括号

修复 python-modernize/python-modernize#178 问题

 # 正确:打印空元组
 print(())
 # 正确:打印元组
 print((1,))
 # 正确:括号内的生成器参数
 sum((i for i in range(3)), [])
 # 修复后:
-print(("foo"))
+print("foo")

常量折叠 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 已弃用的别名

已弃用的 unittest 方法别名 重写为非弃用形式。

 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() 调用

 class C(Base):
     def f(self):
-        super(C, self).f()
+        super().f()

"新式"类

重写类声明

-class C(object): pass
+class C: pass
-class C(B, object): pass
+class C(B): pass

移除 __metaclass__ = type 声明

 class C:
-    __metaclass__ = type

强制 str("native") 字面量

-str()
+''
-str("foo")
+"foo"

.encode("utf-8")

-"foo".encode("utf-8")
+"foo".encode()

# coding: ... 注释

根据 PEP 3120,Python 源代码的默认编码为 UTF-8

-# coding: utf-8
 x = 1

移除 __future__ 导入

可用性:

  • 默认移除 nested_scopesgeneratorswith_statementabsolute_importdivisionprint_functionunicode_literals
  • --py37-plus 还会移除 generator_stop
-from __future__ import with_statement

移除不必要的 py3 兼容性导入

-from io import open
-from six.moves import map
-from builtins import object  # python-future

导入替换

可用性:

  • --py36-plus(及其他)将替换导入

另见 reorder-python-imports

一些例子:

-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

重写 mock 导入

可用性:

-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 和旧 Python3.x 代码块

 import sys
-if sys.version_info < (3,):  # 也理解 `six.PY2`(及其否定)、`six.PY3`(及其否定)
-    print('py2')
-else:
-    print('py3')
+print('py3')

可用性:

  • --py36-plus 将移除 Python <= 3.5 的代码块
  • --py37-plus 将移除 Python <= 3.6 的代码块
  • 以此类推
 # 此示例使用 --py36-plus

 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+')

注意,没有 elseif 块不会被重写,因为这可能会引入语法错误。

移除 six 兼容性代码

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

 # 注意:仅适用于*字面值*
-six.ensure_binary('...')
+b'...'
-six.ensure_str('...')
+'...'
-six.ensure_text('...')
+'...'

open 别名

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

冗余的 open 模式

-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 别名

 # 也理解:
 # - IOError
 # - WindowsError
 # - mmap.error 和使用 `from mmap import error`
 # - select.error 和使用 `from select import error`
 # - socket.error 和使用 `from socket import error`

def throw():

  • raise EnvironmentError('boom')
  • raise OSError('boom')

def catch(): try: throw()

  • except EnvironmentError:
  • except OSError: handle_error()

### `TimeoutError` 别名

可用性:
- `--py310-plus` 用于 `socket.timeout`
- `--py311-plus` 用于 `asyncio.TimeoutError`

```diff

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 别名

-def f(x: Text) -> None:
+def f(x: str) -> None:
    ...

解包列表推导式

-foo, bar, baz = [fn(x) for x in items]
+foo, bar, baz = (fn(x) for x in items)

xml.etree.cElementTree 重写为 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

重写原始类型的 type

-type('')
+str
-type(b'')
+bytes
-type(0)
+int
-type(0.)
+float

typing.NamedTuple / typing.TypedDict py36+ 语法

可用性:

  • 命令行传入 --py36-plus
-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-字符串

可用性:

  • 命令行传入 --py36-plus
-'{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}'

注意: pyupgrade 有意保守,如果它会使表达式变长或者替换参数过于复杂(因为这可能降低可读性),它就不会创建 f-字符串。

subprocess.run: 用 text 替换 universal_newlines

可用性:

  • 命令行传入 --py37-plus
-output = subprocess.run(['foo'], universal_newlines=True)
+output = subprocess.run(['foo'], text=True)

subprocess.run: 用 capture_output=True 替换 stdout=subprocess.PIPE, stderr=subprocess.PIPE

可用性:

  • 命令行传入 --py37-plus
-output = subprocess.run(['foo'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+output = subprocess.run(['foo'], capture_output=True)

移除 @functools.lru_cache() 中的括号

可用性:

  • 命令行传入 --py38-plus
import functools

-@functools.lru_cache()
+@functools.lru_cache
def expensive():
    ...

shlex.join

可用性:

  • 命令行传入 --py38-plus
-' '.join(shlex.quote(arg) for arg in cmd)
+shlex.join(cmd)

用简写替换 @functools.lru_cache(maxsize=None)

可用性:

  • 命令行传入 --py39-plus
import functools

-@functools.lru_cache(maxsize=None)
+@functools.cache
def expensive():
    ...

pep 585 类型注解重写

可用性:

  • 文件导入 from __future__ import annotations
    • 除非命令行传入 --keep-runtime-typing
  • 命令行传入 --py39-plus
-def f(x: List[str]) -> None:
+def f(x: list[str]) -> None:
    ...

pep 604 类型注解重写

可用性:

  • 文件导入 from __future__ import annotations
    • 除非命令行传入 --keep-runtime-typing
  • 命令行传入 --py310-plus
-def f() -> Optional[str]:
+def f() -> str | None:
    ...
-def f() -> Union[int, str]:
+def f() -> int | str:
    ...

pep 696 TypeVar 默认值

可用性:

  • 文件导入 from __future__ import annotations
    • 除非命令行传入 --keep-runtime-typing
  • 命令行传入 --py313-plus
-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

移除带引号的注解

可用性:

  • 文件导入 from __future__ import annotations
-def f(x: 'queue.Queue[int]') -> C:
+def f(x: queue.Queue[int]) -> C:

使用 datetime.UTC 别名

可用性:

  • 命令行传入 --py311-plus
import datetime

-datetime.timezone.utc
+datetime.UTC
项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号