6
\$\开始组\$

所以我刚刚完成了我的第一个Python模块(并在Github上发布),通过这个小项目,我想学习如何分发我的代码,以便其他用户可以将其用作自己项目的插件。

具体来说,我在以下方面寻求反馈:

  • 模块的接口设计是否正确?
  • 在代码的开头,我检查输入的完整性,这是处理错误的最佳方式吗?(看起来很粗)
  • 存储库的设置是否正确,以便实现即插即用?
  • 一般来说,这是设计模块的最佳方法吗?还是我应该使用类而不是函数?

欢迎提供任何其他反馈:)

提前谢谢!


Github存储库链接:https://github.com/nick-van-h/cutlistcalculator网站

__main__.py:

从剪切列表导入getCutLists导入系统导入argparse如果__name__=='__main__':#参数分析器text=“此程序计算梁和板材的最佳切割列表。”parser=argparse。ArgumentParser(description=text)parser.add_argument(“-i”,“--input”,help=“输入json文件的自定义位置(例如'localhost:8080/foo/bar.json'”,default=“”)parser.add_argument(“-o”,“--output”,help=“输出文件夹的自定义位置(例如'localhost:8080/foo'->'localhost:8080/foo/cutlist_result.json'”,default=“”)args=解析器.parse_args()#启动结果=getCutLists(args.input,args.output)#带VS代码解决方案的退出函数尝试:系统退出(结果)除:打印(结果)

剪切列表.py:

导入json从运算符import itemgetter导入副本从pathlib导入路径导入操作系统定义getSolution(reqs,combs):needs=copy.depcopy(需求)res=[]附件([])对于梳子中的梳子:#只要comb[x]中的所有项目满足需求combNeed=真while comb需要:#检查comb[x]是否提供了超出需要的功能(快速故障)需要中的需要:如果梳[need['Length']]>需要['Qty']:combNeed=假如果没有combNeed:打破需要中的需要:need['数量]-=梳[need['Length']]#附加结果res[0].append(comb.copy())#计算总价对于溶胶(单位:res):价格=四舍五入(总和(x['price']代表sol中的x),2)附件([价格])#返回结果返回res定义getCutLists(inputstr=“”,outputstr=“”):如果inputstr:jsonlocation=输入字符串其他:jsonlocation='/input/input.json'#默认输入位置打印(jsonlocation)errstr=“”#获取输入尝试:open(jsonlocation)为f:数据=json.load(f)除:errstr+=“找不到JSON文件。”return(f“错误:{errstr}”)#从JSON对象获取变量尝试:reqs=数据[“所需长度”]除:errstr+=“找不到“所需长度”。”尝试:avail=数据['可用基材']除:errstr+=“找不到可用基材”尝试:cutwidth=数据['减少损失']除:errstr+=“找不到‘削减损失’。”如果errstr:return(f“错误:{errstr}”)#测试数组中所需的键尝试:test=要求中x的[x['Length']如果最小值(测试)<=0:errstr+=f“错误:所需长度({min(test)})必须大于0。”除:errstr+=“在所需的长度中找不到'Length'。”尝试:test=需求中x的[x[“数量”]如果min(测试)<=0:errstr+=f“错误:所需数量({min(test)})必须大于0。”除:errstr+=“在所需长度中找不到‘数量’。”尝试:test=[x['Length'],适用于x如果min(测试)<=0:errstr+=f“错误:可用长度({min(test)})必须大于0。”除:errstr+=“在可用基材中找不到'Length'。”尝试:test=[x[“价格”],适用于x如果min(测试)<0:errstr+=f“错误:可用价格({min(test)})不能为负数。”除:errstr+=“在可用的基础材料中找不到'Price'。”如果errstr:return(f“错误:{errstr}”)#初始化其他变量listreq=[x['Length']forx in reqs]listavail=[x['Length']for x in avail]minreq=最小值(listreq)res=[]#对传递的输入进行错误处理如果max(listreq)>max(listavail):return(f“错误:无法处理,所需长度{max(listreq)}大于长度为{max如果切割宽度<0:return(f“错误:切割宽度不能为负”)#列出所有可用切割组合梳=[]对于有效的平板:myplank=平板复制()对于接入要求:myplack[cut['Length']]=0#增加第一个所需的平板长度myplatk[需求[0][长度]]+=1#设置其他变量myplank['Unitprice']=myplank['Price']/myplank['Length']填充=真填充时:#计算静止长度myplack['Rest']=myplack['Length']对于要求中的i:长度=i['长度']myplack[“休息”]-=((myplack[length]*length)+(myplack[length]*cutwidth))myplack['Rest']+=剪切宽度#设置其余变量myplatk[基本价格]=(myplatk[价格])/((myplatk[长度]-myplatk[休息时间])myplank['Optimal']=(myplack['Rest']<=minreq)#检查静止长度是否为正值如果myplack[“休息”]>=0:combs.append(myplank.copy())myplack[reqs[0]['长度]]+=1其他:对于范围内的i(len(reqs)):如果myplack[reqs[i]['Length']]>0:myplack[reqs[i]['长度]]=0如果i<len(要求)-1:myplack[reqs[i+1]['长度]]+=1打破其他:填充=假#按剩余长度降序排序组合,获得解决方案combs=已排序(combs,key=lambda k:k[“Rest”])res.append(获取解决方案(reqs,combs))#通过先获取最大长度(从最大到最小)、先获取最佳片段来对组合进行排序,然后获取解决方案listreq=已排序(listreq,reverse=True)listreq.insert(0,“最佳”)对于反向的x(listreq):combs.sort(key=itemgetter(x),reverse=True)res.append(获取解决方案(reqs,combs))#按单位最低有效价格对组合进行排序,得到解决方案combs=已排序(combs,key=lambda k:k['Baseprice'])res.append(获取解决方案(reqs,combs))#获取最便宜的选项并制作可读格式最便宜=最小值([x[1]表示x in res])对于分辨率为x的情况:如果x[1]==最便宜:溶胶={}sol['所需基材']={}sol['剪切列表']=[]i=1对于x[0]中的平板:如果板材['Length']不在sol['Required base material']中:sol['所需基材'][plack['长度']]=0sol['所需基材'][plack['长度']]+=1str=f“平板{i}:Length{plack['Length']},”对于需求中的需求:如果plack[req['Length']]>0:str+=f“{plack[leq['Longth'']]}x{req['Length']},”str+=f“休息:{平板['rest']}”sol['剪切列表'].append(str)i+=1sol['总价']=最便宜打破#获取输出位置如果outputstr:outputfile=输出字符串如果输出文件[len(outputfile)-1]!="//":输出文件+=“//”输出文件+=“cutlist_result.json”其他:outputfile=“./output/cutlist_result.json”#生成目录路径(os.Path.dirname(输出文件)).mkdir(parents=True,exist_ok=True)#输出到文件f=打开(输出文件,“w”)json.dump(sol,f,indent=4)f.关闭return(“成功”)
\$\端组\$

2个答案2

重置为默认值
7
\$\开始组\$

位置

位置文档令人困惑。获取剪切列表,输入默认为

'./input/input.json’

但在你的主要的,文档中的示例是

'localhost:8080/foo/bar.json'

这是文件路径还是URL?根据您的使用情况,它看起来必须是一个文件路径,上面显示的主机和端口不应该在那里。也,'./input/input.json’只应设置为默认值输入str,不是"".

函数名称

在Python中,函数和变量名称的标准是lower_snake_case,即。获取输出列表,获取解决方案等。

功能复杂性

获取剪切列表为了可维护性、可测试性和易读性,应至少分为三种不同的功能。

异常处理

不要降级以下字符串的异常:

尝试:...除:errstr+=“找不到JSON文件。”return(f“错误:{errstr}”)

这种模式有一些问题。第一,除:干扰用户Ctrl+C中断程序的能力。也,除:一般来说太宽泛了,在这种情况下,您应该只捕获希望代码抛出的内容文件未找到错误此外,如果您希望错误字符串有用,可以包含文件名。最后,所有这些机器都应该消失,你应该简单地打开()并让异常在没有除了。如果调用方希望重新形成异常在上层打印的方式,则可以;但这不应是该职能部门的责任。在具有良好异常处理的语言中,要避免的一种模式是将异常处理降级为标量返回值(字符串、bool、int错误代码等)

对于这样的验证:

尝试:test=要求中x的[x['Length']如果最小值(测试)<=0:errstr+=f“错误:所需长度({min(test)})必须大于0。”除:errstr+=“在所需的长度中找不到'Length'。”

而是引发您自己的异常:

min_len=min(x['Length']代表需求中的x)如果min_len<=0:raise ValueError(f'所需长度({min_len})必须大于0。')

也不要列出临时清单;应用最小值直接连接到发电机。

评论

鉴于

#列出所有可用切割组合

是一个有用的评论,

#设置其他变量

不是。这比什么都不评论还要糟糕。如果发生了复杂或令人惊讶的事情,或与业务逻辑有关的事情,请记录下来;否则避免

#做这件事do_thing()

表达式简化

((myplank[长度]*长度)+(myplanck[长度]*cutwidth))

可以是

myplack[长度]*(长度+切割宽度)

弱型结构

您正在从JSON加载;好的:但是您永远不会将数据的字典表示解包到对象;你把它放在字典里。这导致代码类似

myplank['Baseprice']=(myplank['Price'])/((myplanck['Length']-myplank['Rest'])

真是一团糟。相反,制作实际的类来表示数据,并将其解压缩。

换句话说,我们不在Javascript中:并不是所有东西都是字典。

混合操作系统/路径

路径(os.Path.dirname(输出文件)).mkdir(parents=True,exist_ok=True)

使用混合路径(良好)和操作系统呼叫(不太好)。你不需要目录名在这里;而不是制造输出文件直接创建路径,然后对其进行操作。

\$\端组\$
2
  • \$\开始组\$ 这比我希望的要多得多,非常感谢!特别是我没有想到会产生自己的错误代码。关于从JSON加载;那么你建议更严格的方法吗? \$\端组\$ 2020年5月26日17:47
  • \$\开始组\$ JSON本身对于存储来说很好,但当您对其进行反序列化时,最好对实际的类进行反序列化。有简单和困难的方法可以做到这一点-简单的方法是打电话cls(字典)在标记的类上@数据类. \$\端组\$
    – 雷德林
    2020年5月26日17:55
\$\开始组\$

源目录

将模块放在单独的源目录中。这样做的优点是可以使用单独安装此目录pip安装-e例如,或通过将其添加到.深度在您的虚拟环境站点包中。您正在使用虚拟环境进行开发?

工具

使用好的IDE和可用的工具来改进代码。我使用黑色作为代码格式化程序,梅比通过严格的配置检查键入错误,蟒蛇样式检查我的文档字符串,pytest测试对于单元测试,pyflakes公司其他错误。了解它们,寻找大型python项目的配置灵感,并将它们集成到您的工作流中。大多数IDE使这一点非常简单。

变量名称

在python中,变量名的长度对程序的性能没有影响。然后选择清晰的变量名,如请求的空格而不是或需求。由于这些不清楚的名称,解密代码真的很困难。

功能中的拆分

您已经有2个函数,但此代码需要更多的函数。

  • 读取输入
  • 验证输入
  • 生成组合
  • 选择一个组合
  • 输出到输出文件

每一个都应该有自己的功能。这样做可以更好地记录这一点,测试不同的部分,并在未来进行更改。

我尝试分离我的函数,以便传输的数据清晰。

读取输入

提升IO(对话:1 2)不要传递输入文件。读取您的main(),函数并将内容传递给验证器和以后的计算。输出也是如此。计算返回所需的木板,然后main()如果需要,函数会将结果写入磁盘。

验证输入

您的输入验证是围绕主方法进行的。您还可以与字符串通信。另一种方法是将验证失败与值错误

如果您添加类型提示和docstring,您可能会得到如下结果:

导入键入class平板(键入.NamedTuple):“”“请求木板。”“”长度:浮子数量:int类BasePlank(键入.NamedTuple):“”“可用的底板。”“”长度:浮子价格:浮点数还是小数?InputData=键入。键入Dict(输入数据,{“削减损失”:浮动,“所需长度”:键入。列表[平板运动],“可用基材”:键入。列表[BasePlank],},)def validate_planks(planks:键入.Iterable[Plank])->无:“”“验证请求的木板。-长度必须大于0-数量必须大于0"""对于木板中的木板:如果“长度”不在平板中:raise ValueError(f“在{plack}中找不到`Length`”)如果“数量”不在板材中:raise ValueError(f“在{platk}中找不到`Qty'”)如果板材[“长度”]<0:raise ValueError(f“`Length`<0 in{plack}”)如果板材[“数量”]<0:提升值错误(f“{plack}中的`Qty`<0”)def validate_baseplanks(板:typeing.Iterable[BasePlank],)->无:“”“验证可用的底板。-长度必须大于0-价格不得为负"""对于木板中的木板:如果“长度”不在平板中:raise ValueError(f“在{plack}中找不到`Length`”)如果“数量”不在板材中:raise ValueError(f“在{plack}中找不到数量”)如果板材[“长度”]<0:raise ValueError(f“`Length`<0 in{plack}”)如果板材[“价格”]<=0:提高价值错误(f“{plack}中的负`Price`”)def validate_input(input_data:InputData)->无:“”“验证输入。”“”如果“削减损失”不在input_data中:raise ValueError(“找不到`Cut loss`”)如果“可用基材”不在input_data中:raise ValueError(“找不到可用基材”)baseplanks=input_data[“可用基材”]验证底板(底板)如果“所需长度”不在input_data中:raise ValueError(“找不到所需长度”)板=input_data[“所需长度”]validate_planks(木板)if max(木板中木板的木板[“长度”])>max(木板[长度]用于垫板中的木板):提升值错误(“要求的最大块数比最长的底板长”)

jsonschema公司

或者您可以使用jsonschema公司为您进行验证:

模式=jsonschema。Draft7验证器({“type”:“对象”,“属性”:{“剪切损失”:{“类型”:“数量”,“最小值”:0},“所需长度”:{“type”:“数组”,“项目”:{“type”:“对象”,“属性”:{“长度”:{“类型”:“数字”,“排除最小值”:0},“数量”:{“type”:“数字”,“exclusiveMinimum”:0,“倍数”:1,},},“必需”:[“长度”,“数量”],},},“可用基材”:{“type”:“数组”,“项目”:{“type”:“对象”,“属性”:{“长度”:{“类型”:“数字”,“排除最小值”:0},“价格”:{“类型”:“数量”,“最小值”:0},},“必需”:[“长度”,“价格”],},“最小属性”:1,},“必需”:[“减少损失”,“可用基材”,“所需长度”,],},})

然后使用

errors=列表(schema.iter_errors(data))

验证完输入数据后,您可以选择将其放入类中,但对于此解决方案,这可能有点太多了。

测试

通过这种方式,您可以单独测试验证。

在单独的目录中测试,文件测试输出列表.py或在每个要测试的函数的单独文件中

导入pytest定义test_validatebaseplanks():correct_data=[{“长度”:300,“价格”:5.95},{“长度”:180,“价格”:2.95},{“长度”:360,“价格”:6.95}]cutlistcalculator.validate_baseplanks(correct_data)missing_price=[{“长度”:300,},{“长度”:180,“价格”:2.95},{“长度”:360,“价格”:6.95}]将pytest.raises(ValueError)作为exinfo:cutlistcalculator.validate_baseplanks(correct_data)在str中断言“未找到价格”(exinfo.value)

等等。

JSON格式

考虑一下要序列化输入和输出的格式。您使用JSON格式,但正如您所注意到的,这有一些缺点。它非常冗长,你不能添加评论。JSON的意思是便于计算机读取。备选方案包括BSON公司,TOML公司, ...

我并不是说这些更好,但至少看看它。特别是当你处于开发的早期,很容易切换。

另一方面,如果您正确地划分代码,并使输入的解析成为自己的函数,那么您可以稍后轻松地更改输入或输出格式。您甚至可以预见多个解析器,并接受不同的格式。

计算

我不明白你用的算法。我没有太多时间去弄清楚,但你用不清楚的名字,把所有的东西都放在一个大的水滴里,这种方式无济于事。尝试将其划分为逻辑结构,然后重构为单独的函数。仔细命名函数,并预见一个docstring并键入hnts。一旦你有了这些,就把它们作为一个新问题再次发布。

制作一个函数,生成可能的切割平面,仅输入所需的板材和可用的基板。把它变成一个生成器,它会产生一个可能的组合。您可以将此管道连接到计算此安排成本的函数中。这将使用一个组合和基板的价格作为参数,并返回组合的成本。通过这样分解工作,您可以记录它们的行为,并可以分别测试每个组件。

输出

将其与计算最佳解决方案的代码分开

使用具有语句来构造上下文。

将output_file.open(“w”)作为文件句柄:json.dump(文件句柄,结果,缩进=2)

结论

我知道这是很多,但请尝试结合这些提示,以及来自Reinderien的提示,如果您不确定是否会返回一个新版本。继续努力

\$\端组\$

你的答案

点击“发布您的答案”,即表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.