2

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

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

文件结构

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

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

开始.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 |')

测试.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

 *   *   *

播放器.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

4 回答 4

5

首先是一些术语:

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

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

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

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

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

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

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

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

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

正如你所看到的,这仍然是一个非常通用的指导方针,但决定什么属于一起不能自动化,最终取决于你自己的判断。

于 2019-01-24T16:04:25.517 回答
3

你目前拥有它的方式很好,我个人更喜欢很多文件,因为它更容易维护。我看到的主要问题是你所有的代码都在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
于 2019-01-24T16:04:40.483 回答
0

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

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

if __name__== '__main__': 

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

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

于 2019-01-24T15:26:57.490 回答
-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")
于 2019-01-24T15:40:55.487 回答