【问题标题】:Is it better to have multiple files for scripts or just one large script file with every function? [closed]是有多个脚本文件还是每个函数只有一个大脚本文件更好? [关闭]
【发布时间】:2019-06-18 09:08:28
【问题描述】:

大约一年前,我开始了一个项目,涉及一个使用 Python 3 的简单的基于终端的 RPG。没有真正考虑它,我就跳入了它。我开始为每个脚本组织多个脚本......好吧,功能。但是在项目进行到一半时,对于最终目标,我不确定只有一个非常大的脚本文件还是多个文件是否更容易/更有效。

由于我在终端上使用了cmd 模块,我意识到让实际的应用程序运行成为一个循环游戏可能对所有这些外部文件都具有挑战性,但同时我有一个@987654324 @ 文件来组合主运行脚本的所有功能。这是文件结构。

澄清一下,我不是最伟大的程序员,而且我是 Python 新手。我还不确定cmd 模块的兼容性问题。

所以我的问题是这样的; 我应该保持这个结构并且它应该按预期工作吗? 或者我应该将所有这些 assets 脚本合并到一个文件中?或者甚至将它们与使用cmd 的start.py 分开?下面是启动函数,加上各种脚本的一些sn-ps。

start.py

from assets import *
from cmd import Cmd
import pickle
from test import TestFunction
import time
import sys
import os.path
import base64

class Grimdawn(Cmd):

    def do_start(self, args):
        """Start a new game with a brand new hero."""
        #fill
    def do_test(self, args):
        """Run a test script. Requires dev password."""
        password = str(base64.b64decode("N0tRMjAxIEJSRU5ORU1BTg=="))
        if len(args) == 0:
            print("Please enter the password for accessing the test script.")
        elif args == password:
            test_args = input('> Enter test command.\n> ')
            try:
                TestFunction(test_args.upper())
            except IndexError:
                print('Enter a command.')
        else:
            print("Incorrect password.")
    def do_quit(self, args):
        """Quits the program."""
        print("Quitting.")
        raise SystemExit


if __name__ == '__main__':

    prompt = Grimdawn()
    prompt.prompt = '> '
    #ADD VERSION SCRIPT TO PULL VERSION FROM FOR PRINT
    prompt.cmdloop('Joshua B - Grimdawn v0.0.3 |')

test.py

from assets import *
def TestFunction(args):
    player1 = BaseCharacter()
    player2 = BerserkerCharacter('Jon', 'Snow')
    player3 = WarriorCharacter('John', 'Smith')
    player4 = ArcherCharacter('Alexandra', 'Bobampkins')
    shop = BaseShop()
    item = BaseItem()
    #//fix this to look neater, maybe import switch case function
    if args == "BASE_OFFENSE":
        print('Base Character: Offensive\n-------------------------\n{}'.format(player1.show_player_stats("offensive")))
        return
    elif args == "BASE_DEFENSE":
        print('Base Character: Defensive\n-------------------------\n{}'.format(player1.show_player_stats("defensive")))
        return

 *   *   *

player.py

#import functions used by script
#random is a math function used for creating random integers
import random
#pickle is for saving/loading/writing/reading files
import pickle
#sys is for system-related functions, such as quitting the program
import sys
#create a class called BaseCharacter, aka an Object()
class BaseCharacter:
    #define what to do when the object is created, or when you call player = BaseCharacter()
    def __init__(self):
        #generate all the stats. these are the default stats, not necessarily used by the final class when player starts to play.
        #round(random.randint(25,215) * 2.5) creates a random number between 25 and 215, multiplies it by 2.5, then roudns it to the nearest whole number
        self.gold = round(random.randint(25, 215) * 2.5)
        self.currentHealth = 100
        self.maxHealth = 100
        self.stamina = 10
        self.resil = 2
        self.armor = 20
        self.strength = 15
        self.agility = 10
        self.criticalChance = 25
        self.spellPower = 15
        self.intellect = 5
        self.speed = 5
        self.first_name = 'New'
        self.last_name = 'Player'
        self.desc = "Base Description"
        self.class_ = None
        self.equipment = [None] * 6
    #define the function to update stats when the class is set
    def updateStats(self, attrs, factors):
        #try to do a function
        try:
            #iterate, or go through data
            for attr, fac in zip(attrs, factors):
                val = getattr(self, attr)
                setattr(self, attr, val * fac)
        #except an error with a value given or not existing values
        except:
            raise("Error updating stats.")
    #print out the stats when called
    #adding the category line in between the ( ) makes it require a parameter when called
    def show_player_stats(self, category):
 *   *   *

注意

脚本的目的是展示它们具有什么样的结构,因此它有助于支持我是否应该合并的问题

【问题讨论】:

    标签: python python-3.x file directory-structure codeanywhere


    【解决方案1】:

    进入单个文件的pythonic方法(我将讨论它主要适用于类)是单个文件是一个模块(不是我之前所说的包)。

    许多工具通常存在于单个包中,但单个模块中的所有工具应始终围绕单个主题。话虽如此,我通常会将一个非常小的项目保存在一个文件中,其中包含几个函数,可能还有几个类。然后我会使用 if main 来包含我希望它完整运行的脚本。

    if __name__== '__main__': 
    

    我会将逻辑分解为尽可能有意义的函数,以便脚本的主体可以作为更高级别的逻辑来阅读。

    简答:每个函数的文件都无法在任何规模上进行管理。您应该将内容放在具有相关功能的文件(模块)中。是否应将当前功能聚集到模块中,由您决定。

    【讨论】:

    • 我想我理解主题的想法,但除此之外我想我不确定如何使用该答案。
    • @TheGamingHideout 答案是它确实取决于项目。增长潜力应该考虑到什么类型的东西会一起去。每个函数都不需要它自己的文件。我将进行编辑以使其更清晰。
    • @XanderYzWich 我完全同意,但在 Python 中,“模块”和“包”是定义明确的术语,因此将一个用于另一个只会让新手感到困惑。
    【解决方案2】:

    有几种方法可以组织您的代码,最终归结为:

    1. 个人喜好
    2. 项目的团队编码标准
    3. 贵公司的命名/结构/架构约定

    我组织 Python 代码的方式是创建多个目录:

    • class_files(可重用代码)
    • input_files(脚本读取的文件)
    • output_files(脚本编写的文件)
    • 脚本(执行的代码)

    这对我很有帮助。相对导入您的路径,以便代码可以从它被克隆的任何地方运行。以下是我在脚本文件中处理导入的方式:

    import sys
    # OS Compatibility for importing Class Files
    if(sys.platform.lower().startswith('linux')):
      sys.path.insert(0,'../class_files/')
    elif(sys.platform.lower().startswith('win')):
      sys.path.insert(0,'..\\class_files\\')
    
    from some_class_file import my_reusable_method
    

    这种方法还可以让您的代码在各种版本的 Python 中运行,并且您的代码可以根据需要进行检测和导入。

    if(sys.version.find('3.4') == 0):
      if(sys.platform.lower().startswith('linux') or sys.platform.lower().startswith('mac')):
                sys.path.insert(0,'../modules/Python34/')
                sys.path.insert(0,'../modules/Python34/certifi/')
                sys.path.insert(0,'../modules/Python34/chardet/')
                sys.path.insert(0,'../modules/Python34/idna/')
                sys.path.insert(0,'../modules/Python34/requests/')
                sys.path.insert(0,'../modules/Python34/urllib3/')
        elif(sys.platform.lower().startswith('win')):
                sys.path.insert(0,'..\\modules\\Python34\\')
                sys.path.insert(0,'..\\modules\\Python34\\certifi\\')
                sys.path.insert(0,'..\\modules\\Python34\\chardet\\')
                sys.path.insert(0,'..\\modules\\Python34\\idna\\')
                sys.path.insert(0,'..\\modules\\Python34\\requests\\')
                sys.path.insert(0,'..\\modules\\Python34\\urllib3\\')
        else:
                print('OS ' + sys.platform + ' is not supported')
    elif(sys.version.find('2.6') == 0):
        if(sys.platform.lower().startswith('linux') or sys.platform.lower().startswith('mac')):
                sys.path.insert(0,'../modules/Python26/')
                sys.path.insert(0,'../modules/Python26/certifi/')
                sys.path.insert(0,'../modules/Python26/chardet/')
                sys.path.insert(0,'../modules/Python26/idna/')
                sys.path.insert(0,'../modules/Python26/requests/')
                sys.path.insert(0,'../modules/Python26/urllib3/')
        elif(sys.platform.lower().startswith('win')):
                sys.path.insert(0,'..\\modules\\Python26\\')
                sys.path.insert(0,'..\\modules\\Python26\\certifi\\')
                sys.path.insert(0,'..\\modules\\Python26\\chardet\\')
                sys.path.insert(0,'..\\modules\\Python26\\idna\\')
                sys.path.insert(0,'..\\modules\\Python26\\requests\\')
                sys.path.insert(0,'..\\modules\\Python26\\urllib3\\')
        else:
                print('OS ' + sys.platform + ' is not supported')
    else:
        print("Your OS and Python Version combination is not yet supported")
    

    【讨论】:

    • 我没有否决它,但我认为它可能归结为sys.path 位。这样做非常不合 Python,如果用户拥有 Python 2.7、3.6、3.7 等会发生什么?即使您对所有代码都进行了硬编码,3.8 出来后您的代码也会再次中断。最好只导入模块,并告诉用户他们需要 pip 安装模块。根本不需要在项目中编辑系统路径:)
    • 感谢@Peter 的反馈。
    【解决方案3】:

    首先是一些术语:

    • “脚本”是旨在直接执行的 python (.py) 文件 (python myscript.py)
    • “模块”是一个 Python 文件(通常包含主要的函数和类定义),旨在由脚本或其他模块导入。
    • “包”是最终包含模块的目录和(在 py2 中是必需的,在 py3 中不是)__init__.py 文件。

    您可以查看教程以获取有关模块和包的更多信息。

    基本上,您想要的是将代码组织成连贯的单元(包/模块/脚本)。

    对于一个完整的应用程序,您通常会有一个“主”模块(不必命名为“main.py”——实际上它通常被命名为应用程序本身),它只会导入一些定义(来自标准库,来自第 3 部分库和您自己的模块),设置并运行应用程序的入口点。在您的示例中,这将是“start.py”脚本。

    对于剩下的代码,你想要的是每个模块具有很强的内聚性(其中定义的函数和类密切相关并且一致实现相同的功能)和低耦合(每个模块尽可能独立于其他模块) )。从技术上讲,您可以在单个模块中放置尽可能多的函数和类,但是太大的模块可能难以维护,因此如果在基于高内聚/低耦合的第一次重组之后,您发现自己拥有 5000+klocs模块,你可能想把它变成一个包含更专业子模块的包。

    如果您仍然有一些实用程序功能显然不适合您的任何模块,通常的解决方案是将它们放在“utils.py”(或“misc.py”或“helpers.py”中"等) 模块。

    您绝对要避免的两件事是:

    1. 循环依赖,直接(模块 A 依赖于模块 B,模块 B 依赖于模块 A)或间接(模块 A 依赖于模块 B,模块 B 依赖于模块 A)。如果您发现自己有这种情况,则意味着您应该将两个模块合并在一起,或者将一些定义提取到第三个模块中。

    2. 通配符导入(“从模块导入 *”),这是一个主要的 PITA wrt/可维护性(您无法从导入中分辨出某些名称是从哪里导入的)并使代码受到意外的影响 - 有时并不明显- 破损

    如您所见,这仍然是一个非常通用的准则,但不能自动决定哪些属于一起,最终取决于您自己的判断。

    【讨论】:

      【解决方案4】:

      您目前拥有它的方式很好,我个人更喜欢大量文件,因为它更容易维护。我看到的主要问题是你所有的代码都在assets下,所以要么你最终会把所有东西都扔在那里(违背了调用它的意义),要么你最终会得到一些一旦您开始编写其他位(例如世界/关卡等),就会出现一堆文件夹。

      一种很常见的设计项目的方法是你的根是Grimdawn,它包含一个文件来调用你的代码,然后你所有的实际代码都放在Grimdawn/grimdawn中。我个人会忘记 assets 文件夹,而是将所有内容都放在该文件夹的根目录下,只有在某些文件变得更复杂或可以分组时才会更深入。

      我会建议这样的事情(以添加一些内容为例):

      Grimdawn/characters/Jon_Snow
      Grimdawn/characters/New_Player
      Grimdawn/start.py
      Grimdawn/grimdawn/utils/(files containing generic functions that are not game specific)
      Grimdawn/grimdawn/classes.py
      Grimdawn/grimdawn/combat.py
      Grimdawn/grimdawn/items.py
      Grimdawn/grimdawn/mobs/generic.py
      Grimdawn/grimdawn/mobs/bosses.py
      Grimdawn/grimdawn/player.py
      Grimdawn/grimdawn/quests/quest1.py
      Grimdawn/grimdawn/quests/quest2.py
      Grimdawn/grimdawn/shops.py
      

      【讨论】:

        猜你喜欢
        • 2016-11-18
        • 1970-01-01
        • 2021-07-07
        • 1970-01-01
        • 1970-01-01
        • 2012-09-26
        • 1970-01-01
        • 2016-06-15
        • 1970-01-01
        相关资源
        最近更新 更多