Python:在单个文件中获取函数和所有依赖项的好方法?

时间:2016-11-07 16:10:32

标签: python pycharm automated-refactoring

我正在研究一个不断发展壮大的Python代码库。它不是一个单独的应用程序 - 更多的共享一些共同代码的实验。

每隔一段时间,我就想公开发布一个给定的实验。我不想发布我可怕的代码库,只是运行给定实验所需的部分。因此,基本上我喜欢抓取所有导入并将所调用的任何函数(或至少导入的所有模块)复制到单个文件中,我可以将其作为演示版本发布。我当然只想对当前项目中定义的文件执行此操作(不是像numpy这样的依赖包)。

我现在正在使用PyCharm,并且无法找到该功能。有没有这样做的工具?

修改:我创建了public-release包以解决此问题。给定一个主模块,它会遍历依赖模块并将它们复制到一个新的仓库中。

5 个答案:

答案 0 :(得分:5)

将所有代码编入单个模块并不是一个好主意。当您的某个实验依赖于具有相同函数名称的不同定义的两个模块时,原因就是一个很好的例子。使用单独的模块,您的代码可以轻松区分它们;将它们填充到同一个模块中,编辑器必须进行某种hacky函数重命名(例如,使用旧的模块名称或其他东西添加它们),如果模块中的某些其他函数调用一个函数,则情况会变得更糟与冲突的名字。您实际上必须完全替换模块范围机制才能执行此操作。

构建模块依赖项列表也是一项非繁琐的任务。考虑进行一项取决于依赖于numpy的模块的实验。您几乎肯定希望您的最终用户实际安装numpy软件包而不是捆绑它,所以现在编辑器必须有一些方法来区分要包含的模块以及您希望以其他方式安装哪些模块。除此之外,您还必须考虑诸如函数导入模块而不是模块顶部和其他不正常情况的情况。

你问的编辑太多了。你真的有两个问题:

  1. 将您的实验代码与发布就绪代码分开。
  2. 打包您的稳定代码。
  3. 分离您的实验代码

    源代码管理是您第一个问题的答案。这将允许您在本地计算机上创建您希望的任何实验代码,并且只要您不提交它,您就不会使用实验代码污染您的代码库。如果您执行想要提交此代码以进行备份,跟踪或共享,则可以在此处使用分支。将分支标识为稳定分支(通常是SVN中的trunk和git中的master),并且只将实验代码提交给其他分支。然后,您可以将实验性功能分支合并到稳定分支中,因为它们已经足够成熟以便发布。如果您愿意,这样的分支设置还有一个额外的好处,即允许您将实验彼此隔离。

    服务器托管的源代码控制系统通常会使事情更简单,更安全,但如果您是唯一的开发人员,您仍然可以在没有服务器的情况下在本地使用git。如果您不是唯一的开发人员,服务器托管存储库还可以更轻松地与其他人协调。

    打包您的稳定代码

    要考虑的一个非常简单的选择是告诉用户从存储库中检出稳定分支。以这种方式分配远非闻所未闻。这仍然比您当前的情况好一点,因为您不再需要手动收集所有文件;但是,您可能需要做一些文档。您也可以使用源代码控制的内置功能将整个提交检出为zip文件或类似文件(SVN中的export和git中的archive)如果您不想公开存储库可用的;这些可以上传到任何地方。

    如果这看起来不够,你现在可以节省时间, setuptools 可能是解决这个问题的好方法。这将允许您生成包含稳定代码的滚轮。您可以为要发布的每个代码包提供一个setup.py脚本; setup.py脚本将标识要包含的包和模块。您必须手动管理此脚本,但如果将其配置为包含整个包和目录,然后为组织代码建立良好的项目约定,则不必经常更改它。这样做的好处是可以为最终用户提供代码的标准安装机制。如果您希望广泛分享,您甚至可以在PyPI上发布它。

    如果您使用setuptools,您可能还需要考虑一个构建服务器,它可以接收新的提交,并可以运行脚本来重新打包并可能发布您的代码。 / p>

答案 1 :(得分:5)

如果您只是想要这些模块,您可以运行代码和新会话,并通过sys.modules查看软件包中的任何模块。

要使用PyCharm移动所有依赖项,您可以创建一个宏,将突出显示的对象移动到预定义文件,将宏附加到键盘快捷键,然后以递归方式快速移动任何项目内导入。例如,我创建了一个名为export_func的宏,它将一个函数移动到to_export.py,并为F10添加了一个快捷方式:

Macro Actions

给定一个我希望在

这样的文件中移动的函数
from utils import factorize


def my_func():
    print(factorize(100))

utils.py看起来像

import numpy as np
from collections import Counter
import sys
if sys.version_info.major >= 3:
    from functools import lru_cache
else:
    from functools32 import lru_cache


PREPROC_CAP = int(1e6)


@lru_cache(10)
def get_primes(n):
    n = int(n)
    sieve = np.ones(n // 3 + (n % 6 == 2), dtype=np.bool)
    for i in range(1, int(n ** 0.5) // 3 + 1):
        if sieve[i]:
            k = 3 * i + 1 | 1
            sieve[k * k // 3::2 * k] = False
            sieve[k * (k - 2 * (i & 1) + 4) // 3::2 * k] = False
    return list(map(int, np.r_[2, 3, ((3 * np.nonzero(sieve)[0][1:] + 1) | 1)]))


@lru_cache(10)
def _get_primes_set(n):
    return set(get_primes(n))


@lru_cache(int(1e6))
def factorize(value):
    if value == 1:
        return Counter()
    if value < PREPROC_CAP and value in _get_primes_set(PREPROC_CAP):
        return Counter([value])
    for p in get_primes(PREPROC_CAP):
        if p ** 2 > value:
            break
        if value % p == 0:
            factors = factorize(value // p).copy()
            factors[p] += 1
            return factors
    for p in range(PREPROC_CAP + 1, int(value ** .5) + 1, 2):
        if value % p == 0:
            factors = factorize(value // p).copy()
            factors[p] += 1
            return factors
    return Counter([value])

我可以突出显示my_func,然后按F10创建to_export.py

from utils import factorize


def my_func():
    print(factorize(100))

factorize中突出显示to_export.py并点击F10

from collections import Counter
from functools import lru_cache

from utils import PREPROC_CAP, _get_primes_set, get_primes


def my_func():
    print(factorize(100))


@lru_cache(int(1e6))
def factorize(value):
    if value == 1:
        return Counter()
    if value < PREPROC_CAP and value in _get_primes_set(PREPROC_CAP):
        return Counter([value])
    for p in get_primes(PREPROC_CAP):
        if p ** 2 > value:
            break
        if value % p == 0:
            factors = factorize(value // p).copy()
            factors[p] += 1
            return factors
    for p in range(PREPROC_CAP + 1, int(value ** .5) + 1, 2):
        if value % p == 0:
            factors = factorize(value // p).copy()
            factors[p] += 1
            return factors
    return Counter([value])

然后突出显示PREPROC_CAP_get_primes_setget_primes以及。{  然后按F10获取

from collections import Counter
from functools import lru_cache

import numpy as np


def my_func():
    print(factorize(100))


@lru_cache(int(1e6))
def factorize(value):
    if value == 1:
        return Counter()
    if value < PREPROC_CAP and value in _get_primes_set(PREPROC_CAP):
        return Counter([value])
    for p in get_primes(PREPROC_CAP):
        if p ** 2 > value:
            break
        if value % p == 0:
            factors = factorize(value // p).copy()
            factors[p] += 1
            return factors
    for p in range(PREPROC_CAP + 1, int(value ** .5) + 1, 2):
        if value % p == 0:
            factors = factorize(value // p).copy()
            factors[p] += 1
            return factors
    return Counter([value])


PREPROC_CAP = int(1e6)


@lru_cache(10)
def _get_primes_set(n):
    return set(get_primes(n))


@lru_cache(10)
def get_primes(n):
    n = int(n)
    sieve = np.ones(n // 3 + (n % 6 == 2), dtype=np.bool)
    for i in range(1, int(n ** 0.5) // 3 + 1):
        if sieve[i]:
            k = 3 * i + 1 | 1
            sieve[k * k // 3::2 * k] = False
            sieve[k * (k - 2 * (i & 1) + 4) // 3::2 * k] = False
    return list(map(int, np.r_[2, 3, ((3 * np.nonzero(sieve)[0][1:] + 1) | 1)]))

即使你有很多你正在复制的代码,它也会非常快。

答案 2 :(得分:2)

所以最后,为了解决我们的问题,我创建了一个名为public-release的工具,它收集了你想要发布的模块的所有依赖项,将它们放入一个带有安装脚本的单独repo中,所以,所以您可以在以后轻松运行代码。

答案 3 :(得分:1)

不幸的是,Python的动态特性使其无法实现。 (例如,您可以通过来自任意来源的名称来调用函数。)

你可以用相反的方式思考,这意味着你应该删除所有未使用的代码部分。

根据this question,PyCharm不支持​​这一点。 vulture包提供了死代码检测功能。

因此,我建议将项目的副本复制到模块中。之后,检测演示代码的所有未使用部分并将其删除。

答案 4 :(得分:0)

在PyCharm中,您可以选择要移动到新模块中的代码,然后从主菜单中选择 - Refactor - &gt;复制(我的F6,但我不记得那是一个自定义的快捷方式)。这使您可以选择将代码复制到您选择的目录中的新(或现有文件)。它还将添加所有相关的导入。