使用Python类使您的代码更加模块化。在我的上一篇文章中,我解释了如何通过使用函数、创建模块或两者来使Python代码更加模块化。函数非常适合避免重复使用多次的代码,而模块确保您可以跨不同项目重用代码。但是还有另一种模块化方法:类。如果您听说过面向对象编程(OOP)一词,那么您可能对类的用途有所了解。程序员倾向于将类视为虚拟对象,有时与物理世界中的某些东西直接相关,有时又是某些编程概念的体现。无论哪种方式,当你想在你的程序中为你或你的程序的其他部分创建“对象”时,你创建一个类来与之交互。没有类的模板假设您正在编写一个以奇幻世界为背景的游戏,并且您需要该应用程序能够产生各种坏人来为玩家的生活带来一些刺激。对函数了解很多,你可能会认为这听起来像是教科书上的函数案例:需要经常重复的代码,但考虑到调用时的变量,只能编写一次。下面一个纯粗基于随机数的敌人生成器现实的例子:#!/usr/bin/envpython3importrandomdefenemy(ancestry,gear):enemy=ancestryweapon=gearhp=random.randrange(0,20)ac=random.randrange(0,20)return[enemy,weapon,hp,ac]deffight(tgt):print("Youtakeaswingatthe"+tgt[0]+".")hit=random.randrange(0,20)ifhit>tgt[3]:print("Youhitthe"+tgt[0]+"for"+str(hit)+"damage!")tgt[2]=tgt[2]-hitelse:print("Youmissed.")foe=enemy("troll","greataxe")print("Youmeeta"+foe[0]+"wieldinga"+foe[1])print("Typethea键,然后返回攻击。”)whileTrue:action=input()ifaction.lower()=="a":fight(foe)iffoe[2]<1:print("你杀死了你的敌人!")else:print("The"+foe[0]+"has"+str(foe[2])+"HPremaining")enemy函数创建一个具有多个属性的敌人,比如血统、武器、生命值、防御等级。它返回每个属性的列表,代表敌人的整体特征。从某种意义上说,这段代码创建了一个对象,即使它还没有使用类。程序员称这个敌人为对象,因为函数的结果(在这种情况下,包含字符串和整数的列表)表示游戏中的单个但复杂的东西。也就是说,列表中的字符串和整数不是任意的:它们一起描述了一个虚拟对象。在编写描述符集时,您可以使用变量,以便在您想要生成敌人时随时使用它们。它有点像模板。在示例代码,当需要一个对象的属性时,检索相应的列表项。例如??,要获取敌人的血统,代码将查询foe[0]、health、foe[2]等。这样没有错方法不,代码按预期工作。Y你可以添加更多不同类型的敌人,创建一个敌人类型列表,并在创建敌人时从列表中随机选择等等,效果很好。实际上,Lua非常高效地利用这个原则来近似一个面向对象的模型。但是,有时对象不仅仅是属性列表。使用对象在Python中,一切都是对象。您在Python中创建的所有内容都是一些预定义的模板实例。甚至基本的字符串和整数都是Pythontype类的派生类。您可以在交互式Pythonshell中看到这一点:>>>foo=3>>>type(foo)>>>foo="bar">>>type(foo)当一个对象被一个类定义时,它不仅仅是一个属性的集合,Python类有它们各自的功能。从逻辑上讲,这很方便,因为只有涉及某个对象类的操作才包含在该对象的类中。在示例代码中,战斗代码是主应用程序的一个函数。这对于一个简单的游戏来说还好,但是对于一个复杂的游戏来说,世界上可能不仅有玩家和敌人,还有城镇居民、牲畜、建筑、森林等等,这些都不需要使用战斗功能。将战斗代码放在敌人类中意味着你的代码更有条理,这在复杂的应用程序中是一个重要的优势。此外,每个类都有访问自己局部变量的特权。比如敌人的生命值,除了某些功能之外,就是不会改变的数据。游戏中的随机蝴蝶不应意外地将敌人的生命值降至0。理想情况下,即使没有课程也不应该发生这种情况。但是在有很多移动部件的复杂应用程序中,这是一个非常有用的技巧,可以确保这种情况永远不会发生在不需要相互交互的部件上。Python类也受到垃圾收集的影响。当一个类的实例不再被使用时,它就会被移出内存。您可能永远不知道这种情况何时会发生,但您通常会知道它何时不会发生,因为您的应用正在占用更多内存并且运行速度更慢。将数据集隔离到类中有助于Python跟踪哪些数据正在使用中,哪些不再需要。优雅的Python这是一个同样简单的格斗游戏,使用Enemy类:#!/usr/bin/envpython3importrandomclassEnemy():def__init__(self,ancestry,gear):self.enemy=ancestryself.weapon=gearself。hp=random.randrange(10,20)self.ac=random.randrange(12,20)self.alive=Truedeffight(self,tgt):print("Youtakeaswingatthe"+self.enemy+".")hit=random.randrange(0,20)ifself.aliveandhit>self.ac:print("Youhitthe"+self.enemy+"for"+str(hit)+"damage!")self.hp=self.hp-hitprint("The"+self.enemy+"has"+str(self.hp)+"HPremaining")else:print("Youmissed.")ifself.hp<1:self.alive=False#游戏开始foe=Enemy("troll","greataxe")print("Youmeeta"+foe.enemy+"wielinga"+foe.weapon)#main函数循环whileTrue:print("TypetheakeyandthenRETURNtoattack.")action=input()ifaction.lower()=="a":foe.fight(foe)iffoe.alive==False:print("Youhavewon...thistime.")exit()这个版本的游戏将敌人视为一个包含相同属性(血统、武器、生命值和防御)的对象,并添加了一个新的来衡量当敌人被击败时,战斗函数类的第一个函数是一个特殊的函数,在Python中称为init或初始化函数。这类似于其他语言中的构造函数,它创建类的实例,您可以通过它的属性和调用类时使用的任何变量来识别它(示例代码中的foe)。Self和类实例类函数接受一种您在类之外看不到的新形式的输入:self。如果不包含self,Python无法知道在调用类函数时使用类的哪个实例。这就像对满屋子的兽人说,“我要和兽人战斗”,对一个兽人说。没有人知道你指的是谁,所有的兽人都出现了。Buch在opengameart.org类的CC-BY-SA中创建的每个属性都以self符号为前缀,它将变量标识为类的属性。派生类的实例后,将self前缀替换为表示该实例的变量。使用这个技巧,你可以在满是兽人的房间里挑战一个兽人,说“我要打一个血统是兽人的兽人”。当兽人听到“gorblar.orc”时,它知道你指的是谁(他自己),所以你会得到一场公平的战斗而不是争吵。在Python中:通过检索类属性(gorblar.enemy或gorblar.hp或您需要的任何对象)而不是查询foe[0](在函数示例中)或gorblar[0]来查找敌人。局部变量如果类中的变量没有前缀self关键字,那么它就是局部变量,就像在函数中一样。例如,无论你做什么,你都不能访问Enemy.fight类之外的hit变量:>>>print(foe.hit)Traceback(mostrecentcalllast):File"./enclass.py",第38行,在print(foe.hit)AttributeError:'Enemy'objecthasnoattribute'hit'>>>print(foe.fight.hit)Traceback(mostrecentcalllast):File"./enclass.py",line38,inprint(foe.fight.hit)AttributeError:'function'objecthasnoattribute'hit'hit变量包含在Enemy类中,只有“存活”时间足够长才能有用在战斗中。更加模块化此示例使用与主应用程序相同的文本文档中的类。在复杂的游戏中,将每个类视为自己独立的应用程序会更容易。当多个开发人员在同一个应用程序上工作时,您会看到这一点:一个开发人员在一个类上工作,另一个开发人员在主程序上工作,只要他们相互交流该类必须具有的属性,他们就可以并行开发这两个代码块。为了使这个示例游戏模块化,它可以分成两个文件:一个用于主应用程序,一个用于类。如果它是一个更复杂的应用程序,您可能每个类有一个文件,或者每个类的逻辑组有一个文件(例如,建筑物文件、自然环境文件、敌人文件或NPC文件等)。将一个仅包含Enemy类的文件保存为enemy.py,将包含其他所有内容的另一个文件保存为main.py。下面是enemy.py:importrandomclassEnemy():def__init__(self,ancestry,gear):self.enemy=ancestryself.weapon=gearself.hp=random.randrange(10,20)self.stg=random.randrange(0,20)self.ac=random.randrange(0,20)self.alive=Truedeffight(self,tgt):print("Youtakeaswingatthe"+self.enemy+".")hit=random.randrange(0,20)ifself.aliveandhit>self.ac:print("Youhitthe"+self.enemy+"for"+str(hit)+"damage!")self.hp=self.hp-hitprint("The"+self.enemy+"has"+str(self.hp)+"HPremaining")else:print("Youmissed.")ifself.hp<1:self.alive=False下面是main.py:#!/usr/bin/envpython3importenemyasen#gamestartfoe=en.Enemy("troll","greataxe")print("Youmeeta"+foe.enemy+"wieldinga"+foe.weapon)#mainloopwhileTrue:print("TypetheakeyandthenRETURNtoattack.")action=input()ifaction.lower()=="a":foe.fight(foe)iffoe.alive==False:print("Youhavewon...thistime.")exit()导入的模块enemy.py使用一个特殊的语句来引用没有具有.py扩展名,后跟您选择的命名空间指示符(例如,importenemyasen)。当您调用该类时,该指示器将在您的代码中使用。除了使用Enemy()之外,您还需要在导入时添加说明符,例如en.Enemy。所有这些文件名都是任意的,但原则上不要使用不寻常的名称。将应用程序的中心命名为main.py是一种常见的约定,一个充满类的文件通常以小写字母命名,类以大写字母开头。是否遵循这些约定并不影响应用程序的工作方式,但它确实使有经验的Python程序员更容易快速理解应用程序的工作方式。在构建代码的方式上有一定的灵活性。例如,使用此示例代码,两个文件必须位于同一目录中。如果只想将类打包为模块,则必须创建一个名为mybad的目录并将类移动到其中。在main.py中,稍微更改您的导入语句:frommybadimportenemyasen两种方法都会产生相同的结果,但是如果您创建的类足够通用,以至于您认为其他开发人员可以在他们的项目中使用它们,那么后者更好。无论您选择哪种方式,您都可以开始游戏的模块化版本:$python3./main.py您遇到了一个挥舞着大斧头的巨魔输入a键然后返回以进行攻击。你对巨魔挥杆。你没打中巨魔。你击中巨魔造成11点伤害!巨魔还剩-7HP你赢了......这次。游戏启动,现在更加模块化。现在您知道面向对象的应用程序意味着什么,但最重要的是,当您向兽人发起决斗时,您知道是哪一个。