python 面向对象编程
在上一篇文章中,我解释了如何通过使用函数,创建模块或同时使用两者来使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]获取健康点,依此类推。
这种方法不一定没有错。 该代码按预期运行。 您可以添加更多不同类型的敌人,可以创建敌人类型列表,并在创建敌人时从列表中随机选择,依此类推。 它运行得很好,实际上, Lua非常有效地使用了这一原理来近似面向对象的模型。
但是,有时对象不仅仅是属性列表。
对象的方式
在Python中,一切都是对象。 您在Python中创建的任何内容都是某些预定义模板的实例 。 甚至基本的字符串和整数都是Python 类型类的派生类。 您可以亲自看到一个交互式Python shell:
>>> foo
=
3
>>>
type
( foo
)
<
class
'int'
>
>>> foo
=
"bar"
>>>
type
( foo
)
<
class
'str'
>
当对象由类定义时,它不仅仅是属性的集合。 Python类具有各自的功能。 从逻辑上讲这很方便,因为仅与某个特定对象类有关的动作包含在该对象的类内。
在示例代码中,斗争代码是主应用程序的功能。 对于一款简单的游戏来说,这很好用,但在复杂的游戏中,游戏世界中不仅会有玩家和敌人。 可能有城镇居民,牲畜,建筑物,森林等,它们都不需具备战斗功能。 将代码放置在敌人的战斗中意味着您的代码组织得更好; 在复杂的应用程序中,这是一个很大的优势。
此外,每个类都有权访问其自己的局部变量。 例如,敌人的健康点不是应该改变的数据,除非通过敌人阶级的某些职能。 游戏中随机出现的蝴蝶不应使敌人的生命值意外降低至0。理想情况下,即使没有阶级,也永远不会发生这种情况,但是在具有大量活动部件的复杂应用程序中,确保部件能够有效地移动是一个强大的诀窍不需要彼此互动,永远也不需要。
Python类也需要进行垃圾回收。 当不再使用类的实例时,会将其移出内存。 您可能永远不知道何时发生这种情况,但是您往往会注意到何时没有发生这种情况,因为您的应用程序占用的内存更多,运行速度也比预期的慢。 将数据集隔离到类中有助于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.
游戏工作。 它是模块化的。 现在,您知道了应用程序面向对象的含义。 但最重要的是,您知道在挑战兽人对决时要具体。
翻译自: https://opensource.com/article/19/7/get-modular-python-classes
python 面向对象编程