- 注重 步骤与过程,不注重职责分工
- 如果需求复杂,代码会变得很复杂
- 开发复杂项目,没有固定的套路,开发难度很大!
- 注重 对象和职责,不同的对象承担不同的职责
- 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
- 需要在面向过程基础上,再学习一些面向对象的语法
- 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
- 定义类的准则
- 继承 实现代码的重用,相同的代码不需要重复的编写
- 设计类的技巧
- 子类针对自己特有的需求,编写特定的代码
-
多态 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果
- 多态 可以 增加代码的灵活度
- 以 继承 和 重写父类方法 为前提
-
是调用方法的技巧,不会影响到类的内部设计
对象类 是对一群具有 相同 特征 或者 行为 的事物的一个统称,是抽象的,不能直接使用
- 特征 被称为 属性
- 行为 被称为 方法
在程序开发中,要设计一个类,通常需要满足一下三个要素:
- 类名 这类事物的名字,满足大驼峰命名法(每一个单词的首字母大写、单词与单词之间没有下划线)
- 属性 这类事物具有什么样的特征
- 方法 这类事物具有什么样的行为
- 对象 是 由类创建出来的一个具体存在,可以直接使用
- 由 哪一个类 创建出来的 对象,就拥有在 哪一个类 中定义的属性、方法
类和对象的关系
- 类是模板,对象 是根据 类 这个模板创建出来的,应该 先有类,再有对象
- 类 只有一个,而 对象 可以有很多个
- 不同的对象 之间 属性 可能会各不相同
- 类 中定义了什么 属性和方法,对象 中就有什么属性和方法,不可能多,也不可能少
面相对象基础语法
dir() 内置函数
- 类是模板,对象 是根据 类 这个模板创建出来的,应该 先有类,再有对象
- 类 只有一个,而 对象 可以有很多个
- 不同的对象 之间 属性 可能会各不相同
- 类 中定义了什么 属性和方法,对象 中就有什么属性和方法,不可能多,也不可能少
在 Python 中 对象几乎是无所不在的,我们之前学习的 变量、数据、函数 都是对象
使用内置函数 dir 传入 标识符 / 数据,可以查看对象内的 所有属性及方法
定义简单的类(只包含方法)
class 类名:
def 方法1(self, 参数列表):
pass
def 方法2(self, 参数列表):
pass
- 方法 的定义格式和之前学习过的函数 几乎一样
- 区别在于第一个参数必须是 self,大家暂时先记住,稍后介绍 self
创建对象
- 方法 的定义格式和之前学习过的函数 几乎一样
- 区别在于第一个参数必须是 self,大家暂时先记住,稍后介绍 self
当一个类定义完成之后,要使用这个类来创建对象,语法格式如下
对象变量 = 类名()
class Cat:
"""这是一个猫类"""
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫在喝水")
tom = Cat()
tom.drink()
tom.eat()
方法中的 self 参数
1、在类的外部给对象增加属性(简单,但不推荐)
在 类的外部的代码 中直接通过 . 设置一个属性即可
tom.name = "Tom"
...
lazy_cat.name = "大懒猫"
2、self 在方法内部输出属性
- 由 哪一个对象 调用的方法,方法内的 self 就是 哪一个对象的引用
- 调用方法时,程序员不需要传递 self 参数
- 在 类的外部,通过 变量名. 访问对象的 属性和方法
- 在 类封装的方法中,通过 self. 访问对象的 属性和方法
class Cat:
def eat(self):
print("%s 爱吃鱼" % self.name)
tom = Cat()
tom.name = "Tom"
tom.eat()
lazy_cat = Cat()
lazy_cat.name = "大懒猫"
lazy_cat.eat()
初始化方法
在 类的外部的代码 中直接通过 . 设置一个属性即可
- 由 哪一个对象 调用的方法,方法内的 self 就是 哪一个对象的引用
- 调用方法时,程序员不需要传递 self 参数
- 在 类的外部,通过 变量名. 访问对象的 属性和方法
- 在 类封装的方法中,通过 self. 访问对象的 属性和方法
1、在初始化方法内部定义属性
- 当使用 类名() 创建对象时,会 自动 执行以下操作:
- 为对象在内存中 分配空间 —— 创建对象
- 为对象的属性 设置初始值 —— 初始化方法(init)
- 这个 初始化方法 就是 __init__ 方法,__init__ 是对象的内置方法
- __init__ 方法是 专门 用来定义一个类 具有哪些属性的方法!;该方法在创建对象时会被自动调用
- 在 __init__ 方法内部使用 self.属性名 = 属性的初始值 就可以 定义属性
- 定义属性之后,再使用类创建的对象,都会拥有该属性
class Cat:
def __init__(self):
print("这是一个初始化方法")
# 定义用 Cat 类创建的猫对象都有一个 name 的属性
self.name = "Tom"
def eat(self):
print("%s 爱吃鱼" % self.name)
# 使用类名()创建对象的时候,会自动调用初始化方法 __init__
tom = Cat()
tom.eat()
2、初始化的同时设置初始值
在开发中,如果在 创建对象的同时,就设置对象的属性,可以做以下操作:
- 把希望设置的属性值,定义成 __init__ 方法的参数
- 在方法内部使用 self.属性 = 形参 接收外部传递的参数
- 在创建对象时,使用 类名(属性1, 属性2...) 调用
class Cat:
def __init__(self, name):
print("初始化方法 %s" % name)
self.name = name
...
tom = Cat("Tom")
...
lazy_cat = Cat("大懒猫")
...
内置方法和属性
__del__ 方法
在 Python 中
- 当使用 类名() 创建对象时,为对象 分配完空间后,自动 调用 __init__ 方法
- 当一个 对象被从内存中销毁 前,会 自动 调用 __del__ 方法
应用场景
- __init__ 改造初始化方法,可以让创建对象更加灵活
- __del__ 如果希望在对象被销毁前,再做一些事情,可以考虑一下 __del__ 方法
生命周期
- 一个对象从调用 类名() 创建,生命周期开始
- 一个对象的 __del__ 方法一旦被调用,生命周期结束
- 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 来了" % self.name)
def __del__(self):
print("%s 去了" % self.name)
# tom 是一个全局变量
tom = Cat("Tom")
print(tom.name)
# del 关键字可以删除一个对象
del tom
print("-" * 50)
__str__ 方法
- 在 Python 中,使用 print 输出 对象变量,默认情况下,会输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)
- 如果在开发中,希望使用 print 输出 对象变量 时,能够打印 自定义的内容,就可以利用 __str__ 这个内置方法了
注意:__str__ 方法必须返回一个字符串
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 来了" % self.name)
def __del__(self):
print("%s 去了" % self.name)
def __str__(self):
return "我是小猫:%s" % self.name
tom = Cat("Tom")
print(tom)
封装
定义没有初始值的属性
- 封装 是面向对象编程的一大特点
- 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中
- 外界 使用 类 创建 对象,然后 让对象调用方法
- 对象方法的细节 都被 封装 在 类的内部
- 一个对象的 属性 可以是 另外一个类创建的对象
注
- 在 对象的方法内部,是可以 直接访问对象的属性 的
- 同一个类 创建的 多个对象 之间,属性 互不干扰!
在定义属性时,如果 不知道设置什么初始值,可以设置为 None
- None 关键字 表示 什么都没有
- 表示一个 空对象,没有方法和属性,是一个特殊的常量
- 可以将 None 赋值给任何一个变量
class Soldier:
def __init__(self, name):
# 姓名
self.name = name
# 枪,士兵初始没有枪 None 关键字表示什么都没有
self.gun = None
身份运算符
身份运算符用于 比较 两个对象的 内存地址 是否一致 —— 是否是对同一个对象的引用
- 在 Python 中针对 None 比较时,建议使用 is 判断
| 运算符 | 描述 | 实例 |
|---|---|---|
| is | is 是判断两个标识符是不是引用同一个对象 | x is y,类似 id(x) == id(y) |
| is not | is not 是判断两个标识符是不是引用不同对象 | x is not y,类似 id(a) != id(b) |
is 与 == 区别:
is 用于判断 两个变量 引用对象是否为同一个 == 用于判断 引用变量的值 是否相等
>>> a = [1, 2, 3] >>> b = [1, 2, 3] >>> b is a False >>> b == a True私有属性和私有方法
- 在实际开发中,对象 的 某些属性或方法 可能只希望 在对象的内部被使用,而 不希望在外部被访问到
- 私有属性 就是 对象 不希望公开的 属性
- 私有方法 就是 对象 不希望公开的 方法
在 定义属性或方法时,在 属性名或者方法名前 增加 两个下划线,定义的就是 私有 属性或方法
class Women:
def __init__(self, name):
self.name = name
self.__age = 18
def __secret(self):
# 在对象的方法内部,是可以访问对象的私有属性的
print("%s 的年龄是 %d" % (self.name, self.__age))
xiaofang = Women("小芳")
# 私有属性,在外界不能够被直接访问
# print(xiaofang.__age)
# 私有方法,同样不允许在外界直接访问
# xiaofang.__secret()
伪私有属性和私有方法(科普)
Python 中,并没有 真正意义 的 私有
- 在给 属性、方法 命名时,实际是对 名称 做了一些特殊处理,使得外界无法访问到
- 处理方式:在 名称 前面加上 _类名 => _类名__名称
# 私有属性,外部不能直接访问到 print(xiaofang._Women__age) # 私有方法,外部不能直接调用 xiaofang._Women__secret()继承
继承的概念、语法和特点
继承的概念
子类 拥有 父类 的所有 方法 和 属性
继承的语法
class 类名(父类名):
pass
- 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
- 子类 中应该根据 职责,封装 子类特有的 属性和方法
继承的传递性
子类 拥有 父类 的所有 方法 和 属性
- 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
- 子类 中应该根据 职责,封装 子类特有的 属性和方法
方法的重写
- C 类从 B 类继承,B 类又从 A 类继承
- 那么 C 类就具有 B 类和 A 类的所有属性和方法
子类 拥有 父类 以及 父类的父类 中封装的所有 属性 和 方法
当 父类 的方法实现不能满足子类需求时,可以对方法进行 重写(override)
重写 父类方法有两种情况:
- 覆盖 父类的方法
- 对父类方法进行 扩展
1、覆盖父类的方法
如果在开发中,父类的方法实现 和 子类的方法实现,完全不同,就可以使用 覆盖 的方式,在子类中 重新编写 父类的方法实现
- 具体的实现方式,就相当于在 子类中 定义了一个 和父类同名的方法并且实现
- 重写之后,在运行时,只会调用 子类中重写的方法,而不再会调用 父类封装的方法
2、对父类方法进行 扩展
如果在开发中,子类的方法实现 中 包含 父类的方法实现(父类原本封装的方法实现 是 子类方法的一部分),就可以使用 扩展 的方式
- 在子类中 重写 父类的方法
- 在需要的位置使用 super().父类方法 来调用父类方法的执行
- 代码其他的位置针对子类的需求,编写 子类特有的代码实现
1)关于 super
- 在 Python 中 super 是一个 特殊的类
- super() 就是使用 super 类创建出来的对象
- 最常 使用的场景就是在 重写父类方法时,调用 在父类中封装的方法实现
2)调用父类方法的另外一种方式(知道)
在 Python 2.x 时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
- 这种方式,目前在 Python 3.x 还支持这种方式
注:
- 在开发时,父类名 和 super() 两种方式不要混用
- 如果使用 当前子类名 调用方法,会形成递归调用,出现死循环
lass Animal:
def eat(self):
print("吃---")
def drink(self):
print("喝---")
def run(self):
print("跑---")
def sleep(self):
print("睡---")
class Dog(Animal):
def bark(self):
print("汪汪叫")
class XiaoTianQuan(Dog):
def fly(self):
print("我会飞")
def bark(self):
# 1. 针对子类特有的需求,编写代码
print("神一样的叫唤...")
# 2. 使用 super(). 调用原本在父类中封装的方法
# super().bark()
# 父类名.方法(self)
Dog.bark(self)
# 注意:如果使用子类调用方法,会出现递归调用 - 死循环!
# XiaoTianQuan.bark(self)
# 3. 增加其他子类的代码
print("$%^*%^$%^#%$%")
xtq = XiaoTianQuan()
# 如果子类中,重写了父类的方法
# 在使用子类对象调用方法时,会调用子类中重写的方法
xtq.bark()
父类的 私有属性 和 私有方法
- 子类对象 不能 在自己的方法内部,直接 访问 父类的 私有属性 或 私有方法
- 子类对象 可以通过 父类 的 公有方法 间接 访问到 私有属性 或 私有方法
- 私有属性、方法 是对象的隐私,不对外公开,外界 以及 子类 都不能直接访问
- 私有属性、方法 通常用于做一些内部的事情
class A:
def __init__(self):
self.num1 = 100
self.__num2 = 200
def __test(self):
print("私有方法 %d %d" % (self.num1, self.__num2))
def test(self):
print("父类的公有方法 %d" % self.__num2)
self.__test()
class B(A):
def demo(self):
# 1. 在子类的对象方法中,不能访问父类的私有属性
# print("访问父类的私有属性 %d" % self.__num2)
# 2. 在子类的对象方法中,不能调用父类的私有方法
# self.__test()
# 3. 访问父类的公有属性
print("子类方法 %d" % self.num1)
# 4. 调用父类的公有方法
self.test()
pass
# 创建一个子类对象
b = B()
print(b)
b.demo()
# 在外界访问父类的公有属性/调用公有方法
# print(b.num1)
# b.test()
# 在外界不能直接访问对象的私有属性/调用私有方法
# print(b.__num2)
# b.__test()
多继承
子类 可以拥有 多个父类,并且具有 所有父类 的 属性 和 方法
语法:
class 子类名(父类名1, 父类名2...)
pass
class A:
def test(self):
print("test 方法")
class B:
def demo(self):
print("demo 方法")
class C(A, B):
"""多继承可以让子类对象,同时具有多个父类的属性和方法"""
pass
# 创建子类对象
c = C()
c.test()
c.demo()
注:如果 父类之间 存在 同名的属性或者方法,应该 尽量避免 使用多继承
Python 中的 MRO —— 方法搜索顺序- Python 中针对 类 提供了一个 内置属性 __mro__ 可以查看 方法 搜索顺序
- MRO 是 method resolution order,主要用于 在多继承时判断 方法、属性 的调用 路径
print(C.__mro__)
输出结果
(, , , )
新式类与旧式(经典)类
- 在搜索方法时,是按照 __mro__ 的输出结果 从左至右 的顺序查找的
- 如果在当前类中 找到方法,就直接执行,不再搜索
- 如果 没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
- 如果找到最后一个类,还没有找到方法,程序报错
object 是 Python为所有对象提供的 基类,提供有一些内置的属性和方法,可以使用 dir 函数查看
- 新式类:以 object 为基类的类,推荐使用
经典类:不以 object 为基类的类,不推荐使用
在 Python 3.x 中定义类时,如果没有指定父类,会 默认使用 object 作为该类的 基类 —— Python 3.x 中定义的类都是 新式类
- 在 Python 2.x 中定义类时,如果没有指定父类,则不会以 object 作为 基类
新式类 和 经典类 在多继承时 —— 会影响到方法的搜索顺序;为了保证编写的代码能够同时在 Python 2.x 和 Python 3.x 运行! 今后在定义类时,如果没有父类,建议统一继承自 object
class 类名(object):
pass
多态
多态 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果
- 多态 可以 增加代码的灵活度
- 以 继承 和 重写父类方法 为前提
-
是调用方法的技巧,不会影响到类的内部设计
多态 更容易编写出出通用的代码,做出通用的编程,以适应需求的不断变化!
class Dog(object):
def __init__(self, name):
self.name = name
def game(self):
print("%s 蹦蹦跳跳的玩耍..." % self.name)
class XiaoTianDog(Dog):
def game(self):
print("%s 飞到天上去玩耍..." % self.name)
class Person(object):
def __init__(self, name):
self.name = name
def game_with_dog(self, dog):
print("%s 和 %s 快乐的玩耍..." % (self.name, dog.name))
# 让狗玩耍
dog.game()
# 1. 创建一个狗对象
# wangcai = Dog("旺财")
wangcai = XiaoTianDog("飞天旺财")
# 2. 创建一个小明对象
xiaoming = Person("小明")
# 3. 让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)
类属性和类方法
实例
- 使用面相对象开发,第 1 步 是设计 类
- 使用 类名() 创建对象,创建对象 的动作有两步:
- 1) 在内存中为对象 分配空间
- 2) 调用初始化方法 __init__ 为 对象初始化
- 对象创建后,内存 中就有了一个对象的 实实在在 的存在 —— 实例
因此,通常也会把:
- 创建出来的 对象 叫做 类 的 实例
- 创建对象的 动作 叫做 实例化
- 对象的属性 叫做 实例属性
- 对象调用的方法 叫做 实例方法
在程序执行时:
- 对象各自拥有自己的 实例属性
- 调用对象方法,可以通过 self.
- 访问自己的属性
- 调用自己的方法
总结
- 每一个对象 都有自己 独立的内存空间,保存各自不同的属性
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用 传递到方法内部
类对象
Python 中 一切皆对象:
- class AAA: 定义的类属于 类对象
- obj1 = AAA() 属于 实例对象
类属性 概念
- 在程序运行时,类 同样 会被加载到内存
- 在 Python 中,类 是一个特殊的对象 —— 类对象
- 在程序运行时,类对象 在内存中 只有一份,使用 一个类 可以创建出 很多个对象实例
- 除了封装 实例 的 属性 和 方法外,类对象 还可以拥有自己的 属性 和 方法
- 类属性
- 类方法
- 类属性 就是给 类对象 中定义的 属性
- 通常用来记录 与这个类相关 的特征
- 类属性 不会用于记录 具体对象的特征
使用 赋值语句 在 class 关键字下方可以定义 类属性
需求
- 定义一个 工具类
- 每件工具都有自己的 name
- 需求 —— 知道使用这个类,创建了多少个工具对象?
class Tool(object):
# 使用赋值语句,定义类属性,记录创建工具对象的总数
count = 0
def __init__(self, name):
self.name = name
# 针对类属性做一个计数+1
Tool.count += 1
# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")
# 知道使用 Tool 类到底创建了多少个对象?
print("现在创建了 %d 个工具" % Tool.count)
属性获取机制
在 Python 中 属性的获取 存在一个 向上查找机制
因此,要访问类属性有两种方式:
- 类名.类属性
- 对象.类属性 (不推荐)
注:如果使用对象.类属性 = 值 赋值语句,只会给对象添加一个属性,而不会影响到类属性的值
类方法 定义语法类方法 就是针对 类对象 定义的方法
@classmethod
def 类方法名(cls):
pass
1.类方法需要用 修饰器 @classmethod 来标识,告诉解释器这是一个类方法
2.类方法的 第一个参数 应该是 cls
- 由 哪一个类 调用的方法,方法内的 cls 就是 哪一个类的引用
- 这个参数和 实例方法 的第一个参数是 self 类似
- 提示 使用其他名称也可以,不过习惯使用 cls
3.通过 类名. 调用 类方法,调用方法时,不需要传递 cls 参数
4.在类方法内部,可以直接使用 cls. 访问 类属性 或者 调用类方法
@classmethod
def show_tool_count(cls):
"""显示工具对象的总数"""
print("工具对象的总数 %d" % cls.count)
静态方法
定义
在开发时,如果需要在 类 中封装一个方法,这个方法:
- 既 不需要 访问 实例属性 或者调用 实例方法
- 也 不需要 访问 类属性 或者调用 类方法
这个时候,可以把这个方法封装成一个 静态方法
语法@staticmethod
def 静态方法名():
pass
综合需求
- 静态方法 需要用 修饰器 @staticmethod 来标识,告诉解释器这是一个静态方法
- 通过 类名. 调用 静态方法
注:静态方法不需要传递第一个参数
- 设计一个 Game 类
- 属性:
- 定义一个 类属性 top_score 记录游戏的 历史最高分
- 定义一个 实例属性 player_name 记录 当前游戏的玩家姓名
- 方法:
- 静态方法 show_help 显示游戏帮助信息
- 类方法 show_top_score 显示历史最高分
- 实例方法 start_game 开始当前玩家的游戏
- 主程序步骤
- 1) 查看帮助信息
- 2) 查看历史最高分
- 3) 创建游戏对象,开始游戏
class Game(object):
# 游戏最高分,类属性
top_score = 0
@staticmethod
def show_help():
print("帮助信息:让僵尸走进房间")
@classmethod
def show_top_score(cls):
print("游戏最高分是 %d" % cls.top_score)
def __init__(self, player_name):
self.player_name = player_name
def start_game(self):
print("[%s] 开始游戏..." % self.player_name)
# 使用类名.修改历史最高分
Game.top_score = 999
# 1. 查看游戏帮助
Game.show_help()
# 2. 查看游戏最高分
Game.show_top_score()
# 3. 创建游戏对象,开始游戏
game = Game("小明")
game.start_game()
# 4. 游戏结束,查看游戏最高分
Game.show_top_score()



