最好有多个脚本文件,或者每个功能只有一个大脚本文件?

时间:2019-01-24 15:19:06

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

大约一年前,我启动了一个项目,该项目涉及一个使用Python 3的简单的基于终端的RPG。我开始为每个脚本组织多个脚本。但是在项目进行到一半时,对于最终目标,我不确定仅拥有一个非常大的脚本文件还是多个文件是否更容易/更有效。

由于我在终端上使用了cmd模块,因此我意识到要使用所有这些外部文件来运行实际的应用程序是一个循环游戏可能会很困难,但是与此同时,我拥有一个__init__.py文件,以合并主要运行脚本的所有功能。这是文件结构。

File Structure

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

所以我的问题是这个; 我应该保持这种结构并且它应该能够按预期工作吗?还是应该将所有这些assets脚本合并到一个文件中?甚至将它们与使用cmd的start.py分开?这是启动功能,还有一些各种脚本的摘要。

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):
 *   *   *

注意

这些脚本的目的是显示它们具有什么样的结构,从而有助于支持我是否应该合并的问题

4 个答案:

答案 0 :(得分:2)

您目前拥有它的方式很好,我个人更喜欢很多文件,因为它更容易维护。我看到的主要问题是您的所有代码都在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

答案 1 :(得分:1)

首先介绍一下术语:

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

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

基本上,您想要的是以统一单位(程序包/模块/脚本)组织代码。

对于完整的应用程序,通常将有一个“主要”模块(不必命名为“ main.py”-实际上通常称为应用程序本身),该模块仅会导入一些定义(来自stdlib) ,从第3部分库以及您自己的模块开始),进行设置并运行应用程序的入口点。在您的示例中,将是“ start.py”脚本。

对于其余的代码,您想要的是每个模块具有强大的内聚性(在其中定义的功能和类紧密相关,并同意实现相同的功能)并且耦合性低(每个模块尽可能独立于其他模块) )。从技术上讲,您可以在单个模块中放置任意数量的函数和类,但是太大的模块可能会难以维护,因此,如果在基于高内聚性/低耦合的第一次重组之后,发现自己拥有5000多个千位数模块,您可能希望将其转换为包含更多专门子模块的软件包。

如果您仍然有几个实用程序函数显然不适合您的任何模块,则通常的解决方案是将它们放到“ utils.py”(或“ misc.py”或“ helpers.py”)中“ etc)模块。

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

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

  2. 通配符导入(“ from module import *”),这是PITA的主要功能/可维护性(您无法从导入中分辨出从何处导入了某些名称),并使代码易于意外-和有时不明显-破损

如您所见,这仍然是一个非常通用的准则,但是不能自动确定所有内容,最后取决于您自己的判断。

答案 2 :(得分:0)

单个文件(我将在很大程度上讨论它的适用范围)的pythonic方法是,单个文件是一个模块(不是我之前所说的包)。

单个套件中通常会存在许多工具,但是单个模块中的所有工具都应围绕单个主题居中。话虽如此,我通常会在一个非常小的项目中将其保存在一个文件中,该文件中包含多个函数,甚至可能包含几个类。然后,我将使用if main来包含脚本,因为我希望它全部运行。

if __name__== '__main__': 

我会尽可能地将逻辑分解为功能,以便使脚本的主体像更高级别的逻辑一样可读。

简短的回答:每个功能的文件都无法以任何规模管理。您应该将它们放到具有相关功能的文件(模块)中。由您决定是否应将当前功能一起聚集到模块中。

答案 3 :(得分:-2)

有几种方法来组织代码,最终归结为:

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

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

Folder Structure for Code Organization

  • 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")