本课程旨在介绍Python编程语言中的面向对象编程(OOP)概念和技术。学生将学习如何使用类、对象、继承、多态等OOP的关键要素来构建灵活、可重用和可扩展的代码。通过实际编程练习和项目,学生将提高他们的编程技能,学会设计和实现面向对象的解决方案。
面向对象编程是在面向过程编程的基础上发展来的,它比面向过程编程具有更强的灵活性和扩展性。面向对象编程是程序员发展的分水岭,很多初学者会因无法理解面向对象而放弃学习编程,所以我们一定要足够重视。
面向对象编程(Object-oriented Programming,简称 OOP)是一种编程范式。
面向对象思想来源于对现实世界的认知。现实世界中每一个事物都是一个对象,它是一种具体的概念。类是人们抽象出来的一个概念,所有拥有相同属性和功能的事物称为一个类;而拥有相同属性和功能的具体事物则成为这个类的实例对象。
现实世界缤纷复杂、种类繁多,难于认识和理解。但是聪明的人们学会了把这些错综复杂的事物进行分类,从而使世界变得井井有条。比如我们由各式各样的汽车抽象出汽车的概念,由形形色色的猫抽象出猫的概念,由五彩斑斓的鲜花抽象出花的概念等。汽车、猫、鲜花都代表着一类事物。每一类事物都有特定的状态,比如汽车的品牌、时速、马力、耗油量、座椅数,小猫的年龄、体重、毛色,鲜花的颜色、花瓣形状、花瓣数目,都是在描述事物的状态。每类事物也都有一定的行为,比如汽车启动、行驶、加速、减速、刹车、停车,猫捉老鼠,鲜花盛开。这些不同的状态
和行为
将各类事物区分开来。
面向对象编程提供了一种从现实世界中抽象出概念和实体的方法。通过类和对象的概念,可以将现实世界中的问题和关系转化为代码结构,使得程序更加符合问题域的模型化。
面向对象编程通过采用类的概念,把事物编写成一个个“类”。在类中,用数据表示事物的状态,用函数实现事物的行为,这样就使编程方式和人的思维方式保持一致,极大的降低了思维难度。
面向对象编程(Object-oriented Programming,简称 OOP),是一种封装代码的方法。其实,在前面章节的学习中,我们已经接触了封装,比如说,将乱七八糟的数据扔进列表中,这就是一种简单的封装,是数据层面的封装;把常用的代码块打包成一个函数,这也是一种封装,是语句层面的封装。
# 声明类
class Dog:
legs_num = 4
has_hair = True
has_tail = True
def bark(self):
print("狗狂吠")
def bite(self):
print("狗咬人")
def fetch(self):
print("狗捡球")
# 实例化对象
alex = Dog()
print(alex.legs_num)
alex.bark()
alex.bite()
class Bird:
legs_nums = 2
has_wings = True
has_teeth = False
def fly(self):
print("鸟飞翔")
def eat_worms(self):
print("鸟吃虫子")
def nest(self):
print("鸟筑巢")
# 实例化对象
b1 = Bird()
print(b1.has_wings)
print(b1.has_teeth)
b1.fly()
面向对象编程(Object-Oriented Programming,简称OOP)相较于面向过程编程(Procedural Programming)有以下几个优点:
总的来说,面向对象编程提供了一种更加结构化、可扩展和可维护的编程范式。它通过封装、继承和多态等特性,使得代码更加模块化、灵活和易于理解。这些优点使得面向对象编程成为当今广泛采用的编程范式之一,被广泛应用于软件开发中。
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Person类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
# 声明类
class 类名:
类属性...
方法...
# 类的实例化
实例对象 = 类名() # 开辟一块独立的属于实例空间,将空间地址作为返回值
# 实例对象可以通过句点符号调用类属性和方法
实例对象.类属性
实例对象.方法(实参)
- 和变量名一样,类名本质上就是一个标识符,命名遵循变量规范。如果由单词构成类名,建议每个单词的首字母大写,其它字母小写。
- 冒号 + 缩进标识类的范围
- 无论是类属性还是类方法,对于类来说,它们都不是必需的,可以有也可以没有。另外,Python 类中属性和方法所在的位置是任意的,即它们之间并没有固定的前后次序。
# 声明类
class Dog:
legs_num = 4
has_hair = True
has_tail = True
def bark(self):
print("狗狂吠")
def bite(self):
print("狗咬人")
def fetch(self):
print("狗捡球")
# 实例化对象
alex = Dog()
print(alex.legs_num)
alex.bark()
alex.bite()
# 实例化对象
peiQi = Dog()
# print(id(alex))
# print(id(peiQi))
print(id(alex.legs_num))
print(id(peiQi.legs_num))
print(id(alex.bark))
print(id(peiQi.bark))
类变量(类属性)的特点是,所有类的实例化对象都同时共享类变量,也就是说,类变量在所有实例化对象中是作为公用资源存在的。实例属性是属于类的每个实例对象的特定属性。实例属性是在创建对象时赋予的,每个对象可以具有不同的实例属性值。
alex = Dog()
peiQi = Dog()
# 实例属性: 属于实例对象自己的属性
alex.name = "李杰"
alex.age = 10
peiQi.name = "武大郎"
peiQi.age = 20
# 问题1:
print(alex.name)
alex.age = 30
print(alex.age)
# 问题2:
print(peiQi.age)
# 问题3:
alex.bark()
alex.bark = "hello world"
# alex.bark()
peiQi.bark()
在 Python 的类定义中,self
是一个特殊的参数,用于表示类的实例对象自身。self
参数必须作为第一个参数出现在类的方法定义中,通常被约定为 self
,但实际上你可以使用其他名称。
当你调用类的方法时,Python 会自动将调用该方法的实例对象传递给 self
参数。这样,你就可以通过 self
参数来引用和操作实例对象的属性和方法。
class Dog:
def eat(self):
print(f"{self.name}正在吃东西。")
def run(self):
print(f"{self.name}正在跑。")
def sleep(self):
print(f"{self.name}正在睡觉。")
def bark(self):
print(f"{self.name}正在狂吠。")
def show_info(self):
print(f"名字:{self.name},品种:{self.breed},颜色:{self.color},年龄:{self.age}")
# 声明对象
bulldog = Dog()
# 赋值实例属性
bulldog.name = "小灰"
bulldog.breed = "斗牛犬"
bulldog.color = "浅灰色"
bulldog.age = 5
# 调用斗牛犬的行为
bulldog.eat()
bulldog.run()
bulldog.sleep()
bulldog.bark()
bulldog.show_info()
__init__
目前的程序:
class Dog:
def eat(self):
print(f"{self.name}正在吃东西。")
def run(self):
print(f"{self.name}正在跑。")
def sleep(self):
print(f"{self.name}正在睡觉。")
def bark(self):
print(f"{self.name}正在狂吠。")
def show_info(self):
print(f"名字:{self.name},品种:{self.breed},颜色:{self.color},年龄:{self.age}")
# 声明对象
bulldog = Dog()
# 赋值实例属性
bulldog.name = "小灰"
bulldog.breed = "斗牛犬"
bulldog.color = "浅灰色"
bulldog.age = 5
# 调用斗牛犬的行为
bulldog.eat()
bulldog.run()
bulldog.sleep()
bulldog.bark()
bulldog.show_info()
# 声明对象
beagle = Dog()
# "小黄","小猎犬", "橘色", 6
# beagle.bark() # 报错
# 赋值实例属性
beagle.name = "小黄"
beagle.breed = "小猎犬"
beagle.color = "橘色"
beagle.age = 6
beagle.bark()
beagle.show_info()
在这套代码中,对象的属性是通过直接赋值给对象的实例属性来实现的,而不是在构造方法中进行初始化。这样做可能会导致以下问题:
为了改进这种写法,可以使用构造方法来初始化对象的属性。构造方法在创建对象时自动调用,并可以接受参数来初始化对象的属性。
class Dog:
def __init__(self, name, breed, color, age):
self.name = name
self.breed = breed
self.color = color
self.age = age
def eat(self):
print(f"{self.name}正在吃东西。")
def run(self):
print(f"{self.name}正在跑。")
def sleep(self):
print(f"{self.name}正在睡觉。")
def bark(self):
print(f"{self.name}正在狂吠。")
def show_info(self):
print(f"名字:{self.name},品种:{self.breed},颜色:{self.color},年龄:{self.age}")
# 声明对象
bulldog = Dog("小灰", "斗牛犬", "浅灰色", 5)
# 调用斗牛犬的行为
bulldog.bark()
bulldog.show_info()
# 声明对象
beagle = Dog("小黄", "小猎犬", "橘色", 6)
beagle.bark()
beagle.show_info()
实例化一个类的过程可以分为以下几个步骤:
__init__
方法,将新创建的对象作为第一个参数(通常命名为self
),并传递其他参数(如果有的话)。__init__
方法中,对对象进行初始化,可以设置对象的属性和执行其他必要的操作。在创建类时,我们可以手动添加一个 __init__()
方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。
__init__()
方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。除了 self 参数外,还可以自定义一些参数,从而完成初始化的工作。
- 注意到
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
是指向创建的实例本身。- 实例属性,实例变量,实例成员变量都是指的存在实例空间的属性
在python语言中,一切皆对象!
我们之前学习过的字符串,列表,字典等等数据都是一个个的类,我们用的所有数据都是一个个具体的实例对象。
区别就是,那些类是在解释器级别注册好的,而现在我们学习的是自定义类,但语法使用都是相同的。所以,我们自定义的类实例对象也可以和其他数据对象一样可以进行传参、赋值等操作。
练习:游戏案例
函数版本:
hero = {
"name": "yuan",
"health": 1000,
"gold": 100,
"defense": 10,
"attack": 90,
"level": 1,
"weapon_list": []
}
# 测试攻击敌人
enemy = {
"name": "alex",
"health": 500,
"defense": 5,
"attack": 50,
"gold": 100,
"level": 1,
"weapon_list": []
}
def attack_enemy(player, enemy):
damage = player["attack"] - enemy["defense"]
if damage > 0:
enemy["health"] -= damage
print(f"{player['name']}成功攻击了敌人{enemy['name']},造成了{damage}点伤害。")
else:
print(f"{player['name']}的攻击被敌人防御了。")
def buy_weapon(player, weapon):
player["weapon_list"].append(weapon)
print(f"{player['name']}购买装备{weapon}!")
def level_up(player):
player["level"] += 1
player["gold"] += 100
print(f"{player['name']}升级了,奖励金币100!")
buy_weapon(hero, "屠龙刀")
attack_enemy(hero, enemy)
level_up(hero)
面向对象版本:
class Weapon:
def __init__(self, name, attack, defense):
self.name = name
self.attack = attack
self.defense = defense
def upgrade(self):
self.attack += 50
self.defense += 50
print(f"{self.name}的防御力增加了50点。")
print(f"{self.name}的防御力增加了50点。")
class Player:
def __init__(self, name, health=100, gold=100, defense=100, attack=100, level=1, weapon_list=[]):
self.name = name
self.health = health
self.gold = gold
self.defense = defense
self.attack = attack
self.level = level
self.weapon_list = weapon_list
def attack_enemy(self, enemy, weapon_index=None):
if weapon_index is None:
damage = self.attack - enemy.defense
else:
damage = self.weapon_list[weapon_index].attack - enemy.defense
if damage > 0:
enemy.health -= damage
print(f"{self.name}成功攻击了敌人{enemy.name},造成了{damage}点伤害。")
else:
print(f"{self.name}的攻击被敌人防御了。")
def buy_weapon(self, weapon):
self.weapon_list.append(weapon)
print(f"{self.name}购买装备{weapon.name}!")
def level_up(self):
self.level += 1
self.gold += 100
print(f"{self.name}升级了,奖励金币100!")
p1 = Player("YUAN")
p2 = Player("ALEX")
p1.attack_enemy(p2)
w1 = Weapon("屠龙刀", 1000, 300)
p1.buy_weapon(w1)
p1.attack_enemy(p2, 0)
class Person:
# 类属性
legs_num = 2
has_emotion = True
def __init__(self, name, age):
self.name = name
self.age = age
def play_fire(self):
print("%s玩火"%self.name)
# 实例对象和类对象可以获取类属性,但是只有类对象才能修改类属性
yuan = Person("yuan", 18)
alvin = Person("alvin", 18)
print(yuan.legs_num)
print(yuan.name)
# Person:一个类对象
print(Person.legs_num)
定义:使用装饰器@staticmethod
。参数随意,没有self
和cls
参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:类对象或实例对象都可以调用。
class Cal():
@staticmethod
def add(x,y):
return x+y
@staticmethod
def mul(x,y):
return x*y
cal=Cal()
print(cal.add(1, 4))
or
print(Cal.add(3,4))
定义:使用装饰器@classmethod
。第一个参数必须是当前类对象,该参数名一般约定为cls
,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:类对象或实例对象都可以调用。
class Student:
# 类属性
cls_number = 68
@classmethod
def add_cls_number(cls):
cls.cls_number+=1
print(cls.cls_number)
Student.add_cls_number()
思考:
类对象.实例方法会怎么样?
类方法的意义是什么,在实例方法中使用类对象变量不可以吗?
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。
class 派生类名(基类名)
...
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。
实际上继承者是被继承者的特殊化,它除了拥有被继承者的特性外,还拥有自己独有得特性。例如猫有抓老鼠、爬树等其他动物没有的特性。同时在继承关系中,继承者完全可以替换被继承者,反之则不可以,例如我们可以说猫是动物,但不能说动物是猫就是这个道理,其实对于这个我们将其称之为“向上转型”。
诚然,继承定义了类如何相互关联,共享特性。对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类或者超类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
同时在使用继承时需要记住三句话:
1、子类拥有父类非私有化的属性和方法。
2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法。(下面会介绍)。
# 无继承方式
class Dog:
def eat(self):
print("eating...")
def sleep(self):
print("sleep...")
def swimming(self):
print("swimming...")
class Cat:
def eat(self):
print("eating...")
def sleep(self):
print("sleep...")
def climb_tree(self):
print("climb_tree...")
# 继承方式
class Animal:
def eat(self):
print("eating...")
def sleep(self):
print("sleep...")
class Dog(Animal):
def swimming(self):
print("toshetou...")
class Cat(Animal):
def climb_tree(self):
print("climb_tree...")
alex = Dog()
alex.run()
class Person(object):
def __init__(self,name,age):
self.name=name
self.age=age
def sleep(self):
print("基类sleep...")
class Emp(Person):
# def __init__(self,name,age,dep):
# self.name = name
# self.age = age
# self.dep = dep
def __init__(self, name, age, dep):
# Person.__init__(self,name,age)
super().__init__(name,age)
self.dep = dep
def sleep(self):
if "不在公司":
# print("子类sleep...")
# 调用父类方法
# 方式1 :父类对象调用 父类对象.方法(self,其他参数)
# Person.sleep(self)
# 方式2: super关键字 super(子类对象,self).方法(参数)or super().方法(参数)
super().sleep()
yuan = Emp("yuan",18,"教学部")
yuan.sleep()
print(yuan.dep)
# 测试题:
class Base:
def __init__(self):
self.func()
def func(self):
print('in base')
class Son(Base):
def func(self):
print('in son')
s = Son()
如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:
class SubClassName (ParentClass1[, ParentClass2, ...]):
...
多继承有什么意义呢?还拿上面的例子来说,蝙蝠和鹰都可以飞,飞的功能就重复定义了。
class Animal:
def eat(self):
print("eating...")
def sleep(self):
print("sleep...")
class Eagle(Animal):
def fly(self):
print("fly...")
class Bat(Animal):
def fly(self):
print("fly...")
有同学肯定想那就放到父类Animal中,可是那样的话其他不会飞的动物还怎么继承Animal呢?所以,这时候多重继承就发挥功能了:
class Fly:
def fly(self):
print("fly...")
class Eagle(Animal,Fly):
pass
class Bat(Animal,Fly):
pass
isinstance
方法class Animal:
def eat(self):
print("eating...")
def sleep(self):
print("sleep...")
class Dog(Animal):
def swim(self):
print("swimming...")
alex = Dog()
mjj = Dog()
print(isinstance(alex,Dog))
print(isinstance(alex,Animal))
print(type(alex))
dir()
方法和__dict__
属性dir(obj)
可以获得对象的所有属性(包含方法)列表, 而obj.__dict__
对象的自定义属性字典
注意事项:
dir(obj)
获取的属性列表中,方法也认为属性的一种。返回的是listobj.__dict__
只能获取自己自定义的属性,系统内置属性无法获取。返回是dict
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def test(self):
pass
yuan = Student("yuan", 100)
print("获取所有的属性列表")
print(dir(yuan))
print("获取自定义属性字段")
print(yuan.__dict__)
其中,类似__xx__
的属性和方法都是有特殊用途的。如果调用len()
函数视图获取一个对象的长度,其实在len()
函数内部会自动去调用该对象的__len__()
方法