如何在代码更改最少的情况下遍历完整的Python脚本?

如何解决如何在代码更改最少的情况下遍历完整的Python脚本?

免责声明:我是科学家,而不是开发人员。我更喜欢可读性和可维护性的代码,但是我编码是为了产生结果,而不是代码。

我经常发现自己经常要运行一个简短的脚本来测试一个或多个参数的影响。通常,要事先知道要更改哪些参数并不容易。

假设我有此伪代码:

INPUT_FILE = "data.csv"
N_COMP = 7
MIN_SIZE = 35
MAX_SIZE = 70
OUTPUT_FILE = f"plot_{N_COMP}.pdf"

data = read(INPUT_FILE)
results = process(data,N_COMP)
figure = plot(results,MIN_SIZE,MAX_SIZE)
store(figure,OUTPUT_FILE)

现在,我想尝试各种N_COMP值的影响。我可以在大多数脚本上添加循环:

INPUT_FILE = "data.csv"
# N_COMP = 7
MIN_SIZE = 35
MAX_SIZE = 70

for N_COMP in (3,5,7,9,11):
  OUTPUT_FILE = f"plot_{N_COMP}.pdf"
  
  data = read(INPUT_FILE)
  results = process(data,N_COMP)
  figure = plot(results,MAX_SIZE)
  store(figure,OUTPUT_FILE)

但是,一旦我想遍历几个变量(甚至可能一次),就变得一团糟,甚至每次我缩进时,甚至都没有提到black对我接近88个字符的行会做些什么再上一层。

我还可以将循环体包装在一个函数中

INPUT_FILE = "data.csv"
MIN_SIZE = 35
MAX_SIZE = 70

def pipeline(N_COMP):
  OUTPUT_FILE = f"plot_{N_COMP}.pdf"

  data = read(INPUT_FILE)
  results = process(data,OUTPUT_FILE)

for N_COMP in (3,11):
  pipeline(N_COMP)

但是,缩进问题仍然存在,现在每次想添加一个我可能想要循环的附加参数时,我都必须在三个位置而不是一个位置添加它。最后,参数的定义在代码中的不同位置。 (我可以通过在顶部定义元组并在循环中重用它来使它变成四个而不是三个。)

所以我正在寻找的是这样的解决方案:

from autoloop import looptuple

INPUT_FILE = "data.csv"
N_COMP = looptuple(3,11)
MIN_SIZE = 35
MAX_SIZE = 70
OUTPUT_FILE = f"plot_{N_COMP}.pdf"

data = read(INPUT_FILE)
results = process(data,OUTPUT_FILE)

这应该执行上面的代码示例所做的工作,每个参数更改一行,并且没有其他缩进。我可以使用不同的命令行来调用此脚本,但是那应该是与参数或参数值无关的通用方法。

(怎么可能)?

解决方法

没有这样的内部“自动循环”方法,尤其是当您的代码未包装到函数中并使用全局变量时。

使用环境变量

影响最小的更改可能是对要更改的参数使用环境变量,例如

import os

INPUT_FILE = "data.csv"
N_COMP = int(os.environ.get("N_COMP",7))
MIN_SIZE = 35
MAX_SIZE = 70
OUTPUT_FILE = f"plot_{N_COMP}.pdf"
# ...

然后,您可以使用以下命令运行脚本(假设使用UNIX-y shell)

env N_COMP=16 python my_script.py

并可能实现自动化,例如

for n_comp in 3 5 7 9 11 42; do 
  env N_COMP=$n_comp python my_script.py
done

使用运行器功能

如果脚本的主体与上面类似,则要在内部对其进行自动化,必须将入口点包装在一个函数中,例如

def run_experiment():
    data = read(INPUT_FILE)
    results = process(data,N_COMP)
    figure = plot(results,MIN_SIZE,MAX_SIZE)
    store(figure,OUTPUT_FILE)

之后,您可以添加运行器功能;稍微滥用globals(),我们可以使它变得更加动态:

import itertools

# Defaults (will be overwritten)
N_COMP = 3
N_KITTENS = 8


def run_experiment():
    print("N_COMP * N_KITTENS:",N_COMP * N_KITTENS)


def run_experiments():
    experiments = {
        "N_COMP": (1,3,5,7),"N_KITTENS": (3,9,42,64),}
    keys,values = zip(*experiments.items())
    for value_combo in itertools.product(*values):
        experiment_values = dict(zip(keys,value_combo))
        # You should add dependent values such as OUTPUT_FILE here
        print("Running:",experiment_values)
        globals().update(experiment_values)
        run_experiment()


if __name__ == "__main__":
    run_experiments()
,

用很多话来说,任何您想重用的东西都应该模块化。第一步是创建一个函数。 (在更复杂的情况下,您可能需要将其放在单独的文件中,和/或使用几种方法创建一个类。)

您要封装内部结构(无论函数内部是什么),但要公开调用方应控制的参数。具体来说,您的函数应接受任何应由外部变量或常量控制的参数作为参数。

(如果您想创建用于对一组参数进行硬编码的其他包装器函数,那很好,但是很方便。)

INPUT_FILE = "data.csv"
MIN_SIZE = 35
MAX_SIZE = 70

def pipeline(n_comp,min_size,max_size,input_file):
  output_file = f"plot_{n_comp}.pdf"

  data = read(input_file)
  results = process(data,n_comp)
  figure = plot(results,max_size)
  store(figure,output_file)

for N_COMP in (3,7,11):
  pipeline(N_COMP,MAX_SIZE,INPUT_FILE)

(可选)您可以为某些参数声明默认值。

def pipeline(n_comp,input_file,min_size=MIN_SIZE,max_size=MAX_SIZE):

您可以使用pipeline(3,"data.csv")来调用它,并且该函数将退回到默认值(对于您未提供的参数)。可选参数必须位于最后,因此我不得不在此处重新排序。

,

这个autoloop.py很好地满足了我的目的:

"""
Upon import,read and execute initial script for autoloop parameter combinations.

Example code:
```
import autoloop

X = autoloop.looptuple(1,2)
Y = autoloop.looprange(10,12)
print(X,Y)
```
"""
import itertools
import os
import re
import sys


def noloop():
    """Make pylint happy."""


def loopdummy(*args):
    """Return first element in case we are not looping."""
    return args[0] if args else None


looptuple = loopdummy
looprange = loopdummy


def find_vars(script,looptype):
    """Parse var = autoloop.looptype(value1,value2,...) lines."""
    pattern = fr"(?P<var>[^\W0-9]\w*)\s*=\s*autoloop\.loop{looptype}\((?P<values>.+)\)"
    matches = [match for line in script if (match := re.fullmatch(pattern,line))]
    variables = [match.group("var") for match in matches]
    value_lists = [re.split(r"\s*,\s*",match.group("values")) for match in matches]

    if looptype == "range":
        value_lists = [range(*map(int,values)) for values in value_lists]

    return (variables,value_lists)


def loop():
    """See module docstring."""
    if sys.argv[0] == __file__:
        return

    with open(sys.argv[0]) as script_file:
        script = script_file.read().splitlines()

    if "autoloop.noloop()" in script:
        return

    (tuple_vars,tuple_value_lists) = find_vars(script,"tuple")
    (range_vars,range_value_lists) = find_vars(script,"range")

    variables = tuple_vars + range_vars
    value_lists = tuple_value_lists + range_value_lists

    # if not variables:
    #     return

    script = [line for line in script if "autoloop" not in line]
    for values in itertools.product(*value_lists):
        preamble = [f"{var} = {value}" for (var,value) in zip(variables,values)]
        print("Exec'ing with",",".join(preamble),"...")
        new_script = "\n".join(itertools.chain(preamble,script))
        exec(new_script,locals(),locals())  # pylint: disable=exec-used
    print("Done.")
    os._exit(0)  # pylint: disable=protected-access


loop()

要使用它,

  • 添加import autoloop(尚未执行任何操作)
  • 转换至少一个常数,例如X = 1X = autoloop.looptuple(1,2,3)

要暂停,

  • 在代码中的任意位置添加autoloop.noloop()(它将使用looptuple / looprange中的第一个值)

示例代码显示:

Exec'ing with X = 1,Y = 10 ...
1 10
Exec'ing with X = 1,Y = 11 ...
1 11
Exec'ing with X = 2,Y = 10 ...
2 10
Exec'ing with X = 2,Y = 11 ...
2 11
Done.

这缺少数十亿张支票。当前,它会将变量添加到顶部,而不是在变量的定义位置,因此依赖项将无法正常工作。当您将autoloop导入到本身已导入的文件中时,它将不起作用。解析最多是最少的。但是对于我的一些小例子来说,它运行良好。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-