本文共 14571 字,大约阅读时间需要 48 分钟。
python 面向对象编程
在上一篇文章中,我解释了如何通过使用函数,创建模块或同时使用两者来 。 为了避免重复使用您打算多次使用的代码,函数是无价的,模块确保您可以在不同项目中使用代码。 但是模块化还有另一个组成部分:类。
如果您听说过术语“ 面向对象的编程” ,那么您可能对用途类有一些概念。 程序员倾向于将类视为虚拟对象,有时与物理世界中的某些事物具有直接相关性,而有时则将其视为某种编程概念的体现。 无论哪种方式,当您想在程序内创建“对象”以供您或该程序的其他部分进行交互时,都可以创建一个类。
这是一个完全基于函数的敌人生成器实现示例:
#!/usr/bin/env python3 import random def enemy ( ancestry , gear ) : enemy = ancestry weapon = gear hp = random . randrange ( 0 , 20 ) ac = random . randrange ( 0 , 20 ) return [ enemy , weapon , hp , ac ] def fight ( tgt ) : print ( "You take a swing at the " + tgt [ 0 ] + "." ) hit = random . randrange ( 0 , 20 ) if hit > tgt [ 3 ] : print ( "You hit the " + tgt [ 0 ] + " for " + str ( hit ) + " damage!" ) tgt [ 2 ] = tgt [ 2 ] - hit else : print ( "You missed." ) foe = enemy ( "troll" , "great axe" ) print ( "You meet a " + foe [ 0 ] + " wielding a " + foe [ 1 ] ) print ( "Type the a key and then RETURN to attack." ) while True : action = input ( ) if action. lower ( ) == "a" : fight ( foe ) if foe [ 2 ] < 1 : print ( "You killed your foe!" ) else : print ( "The " + foe [ 0 ] + " has " + str ( foe [ 2 ] ) + " HP remaining" )
敌人功能可以创建具有多个属性的敌人,例如血统,武器,生命值和防御等级。 它返回每个属性的列表,代表敌人的总数。
从某种意义上说,该代码创建了一个对象,即使它尚未使用类。 程序员称其为“敌人”是一个对象,因为函数的结果(在这种情况下为字符串和整数列表)表示游戏中的一个奇异但复杂的事物 。 也就是说,列表中的字符串和整数不是任意的:它们共同描述了一个虚拟对象。
在编写描述符的集合时,您可以使用变量,以便可以在想要产生敌人的任何时候使用它们。 有点像模板。
在示例代码中,当需要对象的属性时,将检索相应的列表项。 例如,要获取敌人的血统,代码将查看foe [0] ,获取健康点,并查看foe [2]获取健康点,依此类推。
这种方法不一定没有错。 该代码按预期运行。 您可以添加更多不同类型的敌人,可以创建敌人类型列表,并在创建敌人时从列表中随机选择,依此类推。 它运行得很好,实际上, 非常有效地使用了这一原理来近似面向对象的模型。
但是,有时对象不仅仅是属性列表。
在Python中,一切都是对象。 您在Python中创建的任何内容都是某些预定义模板的实例 。 甚至基本的字符串和整数都是Python 类型类的派生类。 您可以亲自看到一个交互式Python shell:
>>> foo = 3 >>> type ( foo ) < class 'int' > >>> foo = "bar" >>> type ( foo ) < class 'str' >
当对象由类定义时,它不仅仅是属性的集合。 Python类具有各自的功能。 从逻辑上讲这很方便,因为仅与某个特定对象类有关的动作包含在该对象的类内。
在示例代码中,斗争代码是主应用程序的功能。 对于一款简单的游戏来说,这很好用,但在复杂的游戏中,游戏世界中不仅会有玩家和敌人。 可能有城镇居民,牲畜,建筑物,森林等,它们都不需具备战斗功能。 将代码放置在敌人的战斗中意味着您的代码组织得更好; 在复杂的应用程序中,这是一个很大的优势。
此外,每个类都有权访问其自己的局部变量。 例如,敌人的健康点不是应该改变的数据,除非通过敌人阶级的某些职能。 游戏中随机出现的蝴蝶不应使敌人的生命值意外降低至0。理想情况下,即使没有阶级,也永远不会发生这种情况,但是在具有大量活动部件的复杂应用程序中,确保部件能够有效地移动是一个强大的诀窍不需要彼此互动,永远也不需要。
Python类也需要进行垃圾回收。 当不再使用类的实例时,会将其移出内存。 您可能永远不知道何时发生这种情况,但是您往往会注意到何时没有发生这种情况,因为您的应用程序占用的内存更多,运行速度也比预期的慢。 将数据集隔离到类中有助于Python跟踪正在使用的内容和不再需要的内容。
这是使用针对敌人的类的简单战斗游戏:
#!/usr/bin/env python3 import random class Enemy ( ) : def __init__ ( self , ancestry , gear ) : self . enemy = ancestry self . weapon = gear self . hp = random . randrange ( 10 , 20 ) self . ac = random . randrange ( 12 , 20 ) self . alive = True def fight ( self , tgt ) : print ( "You take a swing at the " + self . enemy + "." ) hit = random . randrange ( 0 , 20 ) if self . alive and hit > self . ac : print ( "You hit the " + self . enemy + " for " + str ( hit ) + " damage!" ) self . hp = self . hp - hit print ( "The " + self . enemy + " has " + str ( self . hp ) + " HP remaining" ) else : print ( "You missed." ) if self . hp < 1 : self . alive = False # game start foe = Enemy ( "troll" , "great axe" ) print ( "You meet a " + foe. enemy + " wielding a " + foe. weapon ) # main loop while True : print ( "Type the a key and then RETURN to attack." ) action = input ( ) if action. lower ( ) == "a" : foe. fight ( foe ) if foe. alive == False : print ( "You have won...this time." ) exit ( )
此版本的游戏将敌人当作具有相同属性(祖先,武器,健康和防御)的对象来处理,再加上一个新属性来衡量敌人是否已被消灭,并具有战斗功能。
类的第一个功能是一个特殊功能,在Python中称为init或初始化功能。 这类似于其他语言中的 。 它创建了该类的实例,该实例可以通过其属性以及调用该类时使用的任何变量来识别(实例代码中的敌人 )。
类的函数接受一种新的输入形式,您在类外部看不到: self 。 如果您不包括self ,那么在调用类函数时,Python将无法知道要使用该类的哪个实例。 就像在一个充满兽人的房间里说“我将与兽人战斗”,挑战一个兽人决斗。 没有人知道您指的是哪一个,因此发生了坏事。
在类中创建的每个属性都以自我符号开头,该符号将变量标识为类的属性。 生成一个类的实例后,将自己的前缀替换为代表该实例的变量。 使用这种技术,您可以说“我将与gorblar.orc战斗”,从而在一个充满兽人的房间里挑战一个兽人与决斗。 当兽人Gorblar听到gorblar.orc时 ,他知道您指的是哪个兽人(他自己 ),因此您将获得一场公平的战斗,而不是吵架。 在Python中:
gorblar = Enemy ( "orc" , "sword" ) print ( "The " + gorblar. enemy + " has " + str ( gorblar. hp ) + " remaining." )
无需查找敌人类型的foe [0] (或在函数示例中)或gorblar [0] ,而是检索类属性( gorblar.enemy或gorblar.hp或所需的任何对象的任何值)。
如果类中的变量没有在self关键字前加上,则它是局部变量,就像在任何函数中一样。 例如,无论您做什么,都无法访问Enemy.fight类之外的hit变量:
>>> print ( foe. hit ) Traceback ( most recent call last ) : File "./enclass.py" , line 38 , in < module > print ( foe. hit ) AttributeError : 'Enemy' object has no attribute 'hit' >>> print ( foe. fight . hit ) Traceback ( most recent call last ) : File "./enclass.py" , line 38 , in < module > print ( foe. fight . hit ) AttributeError : 'function' object has no attribute 'hit'
hit变量包含在Enemy类中,并且只有“生存”足够长的时间才能在战斗中发挥作用。
本示例使用与主应用程序相同的文本文档中的类。 在复杂的游戏中,将每个类视为自己独立的应用程序要容易得多。 当多个开发人员在同一个应用程序上工作时,您会看到这种情况:一个开发人员在一个类上工作,另一个开发人员在主程序上工作,并且只要他们就类必须具有的属性进行沟通,这两个代码库就可以并行开发。
为了使该示例游戏模块化,将其分为两个文件:一个用于主应用程序,一个用于类。 如果它是一个更复杂的应用程序,则每个类可能有一个文件,或者每个类的逻辑组可能有一个文件(例如,用于建筑物的文件,用于自然环境的文件,用于敌人和NPC的文件等等)。
将仅包含Enemy类的一个文件另存为敌人.py ,将包含其他所有内容的另一个另存为main.py。
这是敌人。py :
import random class Enemy ( ) : def __init__ ( self , ancestry , gear ) : self . enemy = ancestry self . weapon = gear self . hp = random . randrange ( 10 , 20 ) self . stg = random . randrange ( 0 , 20 ) self . ac = random . randrange ( 0 , 20 ) self . alive = True def fight ( self , tgt ) : print ( "You take a swing at the " + self . enemy + "." ) hit = random . randrange ( 0 , 20 ) if self . alive and hit > self . ac : print ( "You hit the " + self . enemy + " for " + str ( hit ) + " damage!" ) self . hp = self . hp - hit print ( "The " + self . enemy + " has " + str ( self . hp ) + " HP remaining" ) else : print ( "You missed." ) if self . hp < 1 : self . alive = False
这是main.py :
#!/usr/bin/env python3 import enemy as en # game start foe = en. Enemy ( "troll" , "great axe" ) print ( "You meet a " + foe. enemy + " wielding a " + foe. weapon ) # main loop while True : print ( "Type the a key and then RETURN to attack." ) action = input ( ) if action. lower ( ) == "a" : foe. fight ( foe ) if foe. alive == False : print ( "You have won...this time." ) exit ( )
导入模块hero.py的过程非常具体,它使用一条声明,将类文件作为其文件名而不带.py扩展名,然后是您选择的命名空间指示符(例如, 将敌人导入为en )。 该标识符是您在调用类时在代码中使用的标识符。 不仅要使用Enemy() , 还要在类的前面加上要导入的内容的名称,例如en.Enemy 。
所有这些文件名都是完全任意的,尽管在原理上并不罕见。 命名应用程序中用作中心集线器main.py的部分是一种常见的约定,充满类的文件通常以小写命名,其中包含所有类,每个类均以大写字母开头。 是否遵循这些约定不会影响应用程序的运行方式,但是它确实使有经验的Python程序员可以更轻松地快速了解应用程序的工作方式。
在结构代码方面有一定的灵活性。 例如,使用代码示例,两个文件必须位于同一目录中。 如果要仅将类打包为模块,则必须创建一个名为mybad的目录, 并将类移入其中。 在main.py中 ,您的import语句稍有变化:
from mybad import enemy as en
两种系统都产生相同的结果,但是如果您创建的类足够通用,以至于您认为其他开发人员可以在他们的项目中使用它们,则后者是最好的。
无论选择哪种,都启动游戏的模块化版本:
$ python3 . / main.py You meet a troll wielding a great axe Type the a key and then RETURN to attack. a You take a swing at the troll. You missed. Type the a key and then RETURN to attack. a You take a swing at the troll. You hit the troll for 8 damage ! The troll has 4 HP remaining Type the a key and then RETURN to attack. a You take a swing at the troll. You hit the troll for 11 damage ! The troll has -7 HP remaining You have won...this time.
游戏工作。 它是模块化的。 现在,您知道了应用程序面向对象的含义。 但最重要的是,您知道在挑战兽人对决时要具体。
翻译自:
python 面向对象编程
转载地址:http://mpbzd.baihongyu.com/