自动更新Python源代码(导入)

gue*_*tli 5 python refactoring automated-refactoring

我们正在重构我们的代码库。

老的:

from a.b import foo_method
Run Code Online (Sandbox Code Playgroud)

新的:

from b.d import bar_method
Run Code Online (Sandbox Code Playgroud)

两种方法 (foo_method()bar_method()) 是相同的。它只是更改了名称和包。

由于上面的示例只是导入方法的多种方式的一个示例,因此我认为简单的正则表达式在这里没有帮助。

如何使用命令行工具重构模块的导入?

很多源代码行需要更改,因此 IDE 在这里没有帮助。

bru*_*off 2

在幕后,IDE 只不过是带有一堆窗口和附加二进制文件的文本编辑器,用于执行不同类型的工作,例如编译、调试、标记代码、linting 等。最终,其中一个库可用于重构代码。Jedi 就是这样的一个库,但还有一个专门用于处理重构的库,那就是Rope

pip3 install rope
Run Code Online (Sandbox Code Playgroud)

CLI 解决方案

您可以尝试使用他们的 API,但由于您要求使用命令行工具,但没有,请将以下文件保存在可访问的任何位置(用户 bin 的已知相对文件夹等)并使其可执行chmod +x pyrename.py

pip3 install rope
Run Code Online (Sandbox Code Playgroud)

让我们创建一个与问题中所示场景完全相同的测试环境(这里假设一个 Linux 用户):

#!/usr/bin/env python3
from rope.base.project import Project
from rope.refactor.rename import Rename
from argparse import ArgumentParser

def renamodule(old, new):
    prj.do(Rename(prj, prj.find_module(old)).get_changes(new))

def renamethod(mod, old, new, instance=None):
    mod = prj.find_module(mod)
    modtxt = mod.read()
    pos, inst = -1, 0
    while True:
        pos = modtxt.find('def '+old+'(', pos+1)
        if pos < 0:
            if instance is None and prepos > 0:
                pos = prepos+4 # instance=None and only one instance found
                break
            print('found', inst, 'instances of method', old+',', ('tell which to rename by using an extra integer argument in the range 0..' if (instance is None) else 'could not use instance=')+str(inst-1))
            pos = -1
            break
        if (type(instance) is int) and inst == instance:
            pos += 4
            break # found
        if instance is None:
            if inst == 0:
                prepos = pos
            else:
                prepos = -1
        inst += 1
    if pos > 0:
        prj.do(Rename(prj, mod, pos).get_changes(new))

argparser = ArgumentParser()
#argparser.add_argument('moduleormethod', choices=['module', 'method'], help='choose between module or method')
subparsers = argparser.add_subparsers()
subparsermod = subparsers.add_parser('module', help='moduledottedpath newname')
subparsermod.add_argument('moduledottedpath', help='old module full dotted path')
subparsermod.add_argument('newname', help='new module name only')
subparsermet = subparsers.add_parser('method', help='moduledottedpath oldname newname')
subparsermet.add_argument('moduledottedpath', help='module full dotted path')
subparsermet.add_argument('oldname', help='old method name')
subparsermet.add_argument('newname', help='new method name')
subparsermet.add_argument('instance', nargs='?', help='instance count')
args = argparser.parse_args()
if 'moduledottedpath' in args:
    prj = Project('.')
    if 'oldname' not in args:
        renamodule(args.moduledottedpath, args.newname)
    else:
        renamethod(args.moduledottedpath, args.oldname, args.newname)
else:
    argparser.error('nothing to do, please choose module or method')
Run Code Online (Sandbox Code Playgroud)

现在使用脚本重命名模块和方法:

# be sure that you are in the project root folder


# rename package (here called module)
../pyrename.py module a b 
# package folder 'a' renamed to 'b' and also all references


# rename module
../pyrename.py module b.b d
# 'b.b' (previous 'a.b') renamed to 'd' and also all references also
# important - oldname is the full dotted path, new name is name only


# rename method
../pyrename.py method b.d foo_method bar_method
# 'foo_method' in package 'b.d' renamed to 'bar_method' and also all references
# important - if there are more than one occurence of 'def foo_method(' in the file,
#             it is necessary to add an extra argument telling which (zero-indexed) instance to use
#             you will be warned if multiple instances are found and you don't include this extra argument


# testing again:
./main.py
# yesterday i was foo, tomorrow i will be bar
cat main.py
cat b/d.py
Run Code Online (Sandbox Code Playgroud)

这个例子确实符合问题的意思。

仅重命名模块和方法,因为它是问题范围。如果您需要更多,您可以增加脚本或从头开始创建一个新脚本,从他们的文档和该脚本本身中学习。为简单起见,我们使用当前文件夹作为项目文件夹,但您可以在脚本中添加额外的参数以使其更加灵活。