OOP: 面向对象编程
2025-02-17
OOP是组织程序的方法,提倡仔细设计和代码重用。
大多数现代编程语言都支持OOP。
本质上说,对象是一组数据以及操作这些数据的函数。
前面学习的,一直在使用python对象,因为数字,字符串,列表,字典和函数都是对象。
要创建新型对象,必须先创建类。
本质上说,类就是设计蓝图,用于创建特定类型的对象。
类指定了 对象 将 包含 那些 数据 和 函数,还指定 对象 和其他 类 之间的关系。对象 封装 了数据以及操作这些数据的 函数 。
一个 重要 的OOP功能是 继承 :
类的继承
- 创建新类时,可以让它 继承 既有类的 数据 和 函数 。这样可以避免重新编写代码。
编写类
- 在OOP语言中,
__init__被成为构造函数,因为它构造对象 - 每次创建新对象时,都将调用构造函数。
- 在Java和C++等语言中,创建对象时需要使用关键字 new
# 介绍OOP,编写一个表示人的类
# 代码
class Person:
'''
Class to represent a person
'''
def __init__(self):
self.name = ''
self.age = 0
# 上面的代码定义了一个名为Person类。
# 它定义了Person对象包含的数据和函数
# 上面的Person类很简单,包含数据name和age
# 当前唯一一个函数时__init__,这是用于初始化对象值的标准函数。
# 当你创建Person对象时,Python将自动调用__init__。方法
- 在类中定义的函数叫做:方法
- 与__init__一样,方法的第一个参数必须时self(会详细地讨论self)
定义的Person类的使用
要常见Person对象,只需调用Person()。
这导致Python运行Person类中的函数
__init__,并返回一个新的Person对象变量age和name包含在对象中,因此每个创建的Person对象都有自己的name和age
要访问name和age,必须使句点表示法指定存储他们的对象
## 下面的方法可以使用定义Person类
p = Person()
print(p)
print(p.age)
print(p.name)
p.age = 35
print(p.age)
p.name = 'Nome'
print(p.name)
## 在这个例子中。变量p 指向一个 Person对象。
## 从 Person类的定义可知,Person对象包含变量 age 和 name
## 可以像使用常规变量那样使用。
## 但必须使用句点表示法:即: p.age 和 p.name
## Python自动给每个对象添加特殊变量self
## 这个self变量指向本省,让类中的函数能够明确地引用对象地数据和函数
'''
<__main__.Person object at 0x06B88130>
0
35
Nome
'''参数self
- 在上面的例子中,调用Person()时,没有提供任何参数,但函数
__init__(self)期望获得名为self的输入。 - 这是因为在OOP中,
self是一个指向对象本身的变量。概念简单。理解不容易
提示
所有类都应该有方法
__init__(self),这个方法的职责是初始化对象,如初始化对象的变量。方法
__init__仅被调用一次-------在对象被创建时。可根据需要给
__init__指定其他参数遵循Python的标准做法,将
__init__的第一个参数命名为self。并非必须这样做,可根据自己的喜好使用任何变量名(而不是self)。
在python中,可像其他数据类型一样使用对象:可将他们传递给函数,存储到列表和字典中、保存到文件中等等
显示对象
- 方法是类中定义地函数。
- 下面给Person类添加一个方法,用于打印Person对象的内容
## 打印 Person对象的内容
class Person:
'''
Class to represent a person
'''
def __init__(self):
self.name = ' '
self.age = 0
def display(self):
print("Person('%s',%d)" % (self.name, self.age))
# 方法display 将Person 对象的内容以适合程序员阅读的格式打印到屏幕上
p = Person()
p.display()
p.name = 'Bob'
p.age = 35
p.display()
'''
Person(' ',0)
Person('Bob',35)
'''## 方法display的效果很好,但还可以使用下面更好的方法
## python提供了一些特殊方法,让你能够定制对象以支持天衣无缝的打印
## 例如,特殊方法 __str__ 用于生成对象的字符串表示
class Persom():
'''
Class to represent a person
'''
def __init__(self):
self.name = ' '
self.age = 0
def display(self):
print("Person('%s',%d)" % (self.name,self.age))
def __str__(self):
return "Person('%s',%d) % (self.name,self.age)"
g = Person()
print(str(g))
## 还可以使用str简化display
class Persom():
'''
Class to represent a person
'''
def __init__(self):
self.name = ' '
self.age = 0
def display(self):
print(str(self))
def __str__(self):
return "Person('%s',%d) % (self.name,self.age)"
f = Person()
f.name = 'Mon'
f.age = 35
print(str(f))
print(display())
## 还可以定义特殊方法 __repr__ 返回对象的‘官方’(official)表示。
## 如:Person对象的默认官方表示不太实用
aaa = Person()
print(aaa)
## 通过添加方法 __repr__ 可控制这里打印的字符串。大大多数类中,方法 __repr__ 都与方法 __str__ 相同
class Persom():
'''
Class to represent a person
'''
def __init__(self):
self.name = ' '
self.age = 0
def display(self):
print(str(self))
def __str__(self):
return "Person('%s',age) % (self.name,self.age)"
def __repr__(self):
return str(self)
ccccc = Person()
print(ccccc)
print(str(ccccc))
## 创建自己的类和对象时,编写函数__str__ 和 __repr__ 几乎总是值得的。
## 对于显示对象的内容很有帮助,而显示对象的内容有助于调试程序
## 当定义了方法__repr__,但没有定义方法 __str__,则对象调用str()时,将执行__repr__
## 添加方法 __repr__ 后,可进一步简化发放display
## def display(self):
## print(self)
## 实际上,没有必要编写方法display
## python建议将对象的字符串表示设置为创建对象所需的代码。
## 这种约定,能够轻松地创建对象---只需要将字符串表示复制并粘贴到命令行
'''
Person('',0)
Person('',35)
None
Person('',0)
Person('',0)
Person('',0)
'''灵活的初始化
- 当需要创建特定的姓名和年龄的Person对象,可以用一个比较的方法是,在构造对象时将姓名和年龄传递给
__init__
## 重写__init__
class Person:
def __init__(self,name = '',age = 0):
self.name = name
self.age = age
def __str__(self):
return "Person('%s',age) % (self.name,self.age)"
def __repr__(self):
return str(self)
cde = Person('Moe',66)
cde
# Person('%s',age) % (self.name,self.age)特性装饰器
- 特性装饰器 融 变量的简洁与函数的灵活于一身。
- 装饰器指出函数或方法有点特点,这是使用它们来指示设置函数和获取函数
- 获取函数返回变量的值,将使用@property装饰器来指出
## 使用@property装饰器来指出
@property
def age(self):
'''
Return this person's age.
'''
return self._age
## 这个age方法除必不可少的self外,不接受任何参数。
## 我们在它前面加上了 @property,指出这是一个获取函数。这个方法的名称将被用于设置变量
## 还将底层变量self.age重命名为:self._age
## 在对象变量前加上下划线是一种常见的作法。
## 这里使用这个变量与方法age区分
## 修改Person类为下面的样子:
class Person:
def __init__(self,name = '',age = 0):
self._name = name
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self,age):
if 0 < age <= 100:
self._age = age
def __str__(self):
return "Person('%s',%s)" % (self._name,self._age)
def __repr__(self):
return str(self)
m = Person('Lia',33)
print(m)
m.age = 55
print(m.age)
m.age = -4
print(m.age)
n = Person('lin',25)
print(n)
n.age = 30
print(n)
n.age = -6
print(n)
## 给age提供了设置函数和获取函数,编写的代码就像直接使用变量age
## 差别在于:遇到代码m.age = -4/-6 时,python实际上将调用方法 age(self,age)
## 同样,遇到代码m.age时,将调用函数age(self,age)
## 这样的好处: 赋值语法简单,同时可控制变量的设置和获取方式
'''
Person('Lia',33)
55
55
Person('lin',25)
Person('lin',30)
Person('lin',30)
'''私有变量
- 不以下划线打头的变量是公有变量,任何代码都可以访问它们。
- 为降低self._age被直接修改的可能性。主要有以下几种方式
- 将其重命名为
self.__age,即在变量名开头包含两个下划线。两个下划线表明:age是私有变量,不应在Person类外直接访问它。
要直接访问
self.__age,需要在前面加上_Personp._Person__age = -44 print(p)
这降低了直接修改的可能性
- 将其重命名为
在编写大型程序时,一条使用的经验是:首先将所有对象变量都设置为私有的。当有充分理由的情况下将其改为公有的。
继承
- 继承是一种重用类的集之,能够这样创建全新的类:给既有类的拷贝添加变量和方法
- 当需要开发一款游戏,其中设计人类玩家和计算机玩家。为此,可以创建一个Player类,
- 这个Player类。包含所有玩家都有的东西
术语
在下面的例子中
我们可以有下面的说法:
- Human 扩展了 Player
- Human 从 Player 派生而来
- Human 是 Player 的字类。而 Player 是 Human 的超类
- Human 是一个 Player
最后一个术语,意味着所有人都是玩家。
要创建类层次结构,一种方法是考虑类之间可能存在的是一个关系
语法
class New_name(Load_name)
- New_name: 表示的是:定义的新类的名称
- Load_name:表示的是:之前的或者存在的类的名称# 案例 Player类
class Player:
def __init__(self,name):
self._name = name
self._score = 0
def reset_score(self):
self._score = 0
def incr_score(self):
self._score = self._score + 1
def get_name(self):
return self._name
def __str__(self):
return "name = '%s',score = %s" % (self._name,self._score)
def __repr__(self):
return 'Player(%s)' % str(self)
abcdfg = Player('Moe')
print(abcdfg)
abcdfg.incr_score()
print(abcdfg)
abcdfg.reset_score()
print(abcdfg)
# name = 'Moe',score = 0
# name = 'Moe',score = 1
# name = 'Moe',score = 0## 上面中,我们创建了一个类Player,下面,我们创建类Human继承Player类
## 创建一个Human类,表示人类玩家,一种办法是通过复制并粘贴新建Player类的一个拷贝,
## 在添加让玩家走棋的方法 make_move(self)。
## 另一种方法,就是使用继承,让Human类继承Player类的所有变量和方法。就不需要在进行编写
class Human(Player):
pass
# 在Python中,pass语句表示“什么都不做”,对Human类来说,是一个完整而实用的定义。
hfg = Human('zhangsan')
print(hfg)
hfg.incr_score()
print(hfg)
hfg.reset_score()
print(hfg)
# name = 'zhangsan',score = 0
# name = 'zhangsan',score = 1
# name = 'zhangsan',score = 0
# 重写方法
# 上面的例子中。h 的字符串表示为 Player,但更准确的说法,h应该是Human。
# 修复这样的问题,可给Human定义 __repr__
class Human(Player):
def __repe__(self):
return 'Human(%s)' % str(self)
h = Human("zhangsan")
print(h)
# 创建 Computer 类
class Computer(Player):
def __repr__(self):
return 'Computer(%s)' % str(self)
# name = 'zhangsan',score = 0多态
- 演示 OOP 的强大
- 创建一个名为 Undercut的简单游戏。
- 这个游戏中,两玩家同时选择一个1--10的整数
- 如果一个玩家选择的整数比对方选择整数的小1,则该玩家获胜,否则平手
## Undercut游戏 案例
class Player:
def __init__(self,name):
self._name = name
self._score = 0
def reset_score(self):
self._score = 0
def incr_score(self):
self._score = self._score + 1
def get_name(self):
return self._name
def __str__(self):
return "name = '%s',score = %s" % (self._name,self._score)
def __repr__(self):
return 'Player(%s)' % str(self)
def play_undercut(p1,p2):
p1.reset_score()
p2.reset_score()
m1 = p1.get_move()
m2 = p2.get_move()
print("%s move: %s" % (p1.get_name(),m1))
print("%s move: %s" % (p2.get_name(),m2))
if m1 == m2 -1:
p1.incr_score()
return p1,p2,'%s wins!' % p1.get_name()
elif m2 == m1 -1:
p2.incr_score()
return p1,p2,'%s wins!' % p2.get_name()
else:
return p1,p2,'draw: no winner'
## 在上面的函数中,调用了p1.get_move()和p2.get_move()
## 下面来实现这些函数
## 由于在游戏Undercut中,走法不过是选择1--10的数字
## 但是人类玩家是通过键盘键入一个1--10的数字,而计算机使用函数选择数字
## 因此 Human 和 Computer 类需要专用的get_move(self)方法
## 下面为Human类的方法 get_move
class Human(Player):
def __repr__(self):
return 'Human(%s)' % str(self)
def get_move(self):
while True:
try:
n = int(input('%s move (1 - 10):' % self.get_name()))
if 1 <= n <= 10:
return n
else:
print('Oops!')
except:
print('Oops!')
# 上面的功能就是要求用户一致输入时正确的1 -- 10 的整数为止
# try/except 结构用于捕获异常。
import random
class Computer(Player):
def __repr__(self):
return 'Computer(%s)' % str(self)
def get_move(self):
return random.randint(1,10)
## 代码基本上完成了,下面进行游戏的试玩
jisuanji = Computer('Hal Bot')
renlei = Human('lia')
play_undercut(jisuanji,renlei)
'''
lia move (1 - 10):4
Hal Bot move: 8
lia move: 4
(Computer(name = 'Hal Bot',score = 0),
Human(name = 'lia',score = 0),
'draw: no winner')
'''