01 . 类和对象
1.1 类
类是对一群具有相同属性特征或者行为的事物的一个统称,是抽象的,不能直接使用。
- 特征被称为属性
- 行为被称为方法
类的三要素:
- 类名(满足大驼峰命名法)
- 属性(这类事物具有什么样的特征)
- 方法(这类事物具有什么样的行为)
-大驼峰命名法:每个单词首字母大写,单词与单词之间没有下划线
类的创建
class Student: //Student为类的名称(类名)由一个或多个单词组成,每个单词的首字母大写,其余小写
pass
类的组成:类属性,实例方法,静态方法,类方法
1.2 对象
- 对象是由类创建出来的一个具体存在,可以直接使用
- 由哪一个类创建出来的对象,就拥有哪一个类中的定义:
- 属性
- 方法
- 先有类,再有对象
- 类只有一个,而对象可以有多个(不同对象之间属性可能会各不相同)
02 . 面向对象基本语法
面向对象三大特性
- 封装 根据职责实现将属性和方法 封装到一个抽象的类中
- 继承 实现代码的重用,相同的代码不需要重复的编写
- 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活性
2.1 dir内置函数
# 语法:dir(函数名)
def demo():
pass
dir(demo)
2.2 定义简单的类(只包含方法)
class 类名:
def 方法1(self, 参数列表):
pass
def 方法2(self, 参数列表):
pass
方法的定义格式和函数几乎一样,区别在于第一个参数必须是self
2.3 创建对象
对象变量 = 类名()
2.4 第一个面向对象程序
class Cat:
def eat(self):
print('小猫爱吃鱼')
def drink(self):
print('小猫要喝水')
# 创建猫对象
tom = Cat()
tom.eat()
tom.drink()
------案例进阶------
class Cat:
def eat(self):
print('小猫爱吃鱼')
def drink(self):
print('小猫要喝水')
# 创建猫对象
tom = Cat()
tom.eat()
tom.drink()
print(tom)
# 再创建一个猫对象
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
print(lazy_cat)
lazy_cat2 = lazy_cat
print(lazy_cat2)
# 输出结果
小猫爱吃鱼
小猫要喝水
<__main__.Cat object at 0x00000223053A9460>
小猫爱吃鱼
小猫要喝水
<__main__.Cat object at 0x0000022305491130># lazy_cat和tom不是同一只猫(使用同一个类可以创建出多个对象)
<__main__.Cat object at 0x0000022305491130># lazy_cat和lazy_cat2表示的是同一只猫
2.5 方法中的self参数
2.5.1 案例改造----给对象增加属性
- 只需要在类的外部的代码中直接通过 . 设置一个属性即可(但是不推荐使用)
tom.name = "Tom"
...
lazy_cat.name = "大懒猫"
2.5.2 使用self在方法内部输出每一只猫的名字
由哪一个对象调用的方法,方法内的 self 就是哪一个对象的引用
- 在类封装的方法内部,self 就表示当前调用方法的对象自己
- 调用方法时,程序员不需要传递self参数
- 在方法内部
- 可以通过 self. 访问对象的属性
- 也可以通过 self. 调用其他的对象方法
class Cat:
def eat(self):
# 哪一个对象调用的方法,self就是哪一个对象的引用
print('%s爱吃鱼' % self.name)
def drink(self):
print('%s要喝水' % self.name)
# 创建猫对象
tom = Cat()
# 可以使用 .属性名 利用赋值语句就可以了
tom.name = "Tom"
tom.eat()
tom.drink()
# 再创建一个猫对象
lazy_cat = Cat()
lazy_cat.name = "大懒猫"
lazy_cat.eat()
lazy_cat.drink()
lazy_cat2 = lazy_cat
# 输出结果
Tom爱吃鱼
Tom要喝水
大懒猫爱吃鱼
大懒猫要喝水
2.6 初始化方法
2.6.1 之前代码存在的问题----在类的外部给对象增加属性
- 将案例代码进行调整,先调用方法,再设置属性
tom = Cat()
# tom.name = "Tom"
tom.eat()
tom.drink()
tom.name = "Tom"
# 输出
AttributeError: 'Cat' object has no attribute 'name'
属性错误:'Cat'对象没有'name'属性
- 对象应该包含哪些属性,应该封装在类的内部
2.6.2 初始化方法
- 当使用 类名() 创建对象时,会自动执行以下操作:
- 为对象在内存中分配空间----创建对象
- 为对象的属性设置初始值----初始化方法(init)
- 这个初始化方法就是__init__方法,__init__是对象的内置方法
- __init__方法是专门用来定义一个类具有哪些属性的方法
class Cat:
def __init__(self):
print('这是一个初始化方法')
# 使用类名()创建对象时,会自动调用初始化方法
tom = Cat()
# 输出
这是一个初始化方法
2.6.3 在初始化方法内部定义属性
- 在__init__方法内部使用 self.属性名 = 属性的初始值 就可以定义属性
- 定义属性之后,再使用Cat类创建的对象,就会拥有该属性
class Cat:
def __init__(self):
print('这是一个初始化方法')
# 定义用Cat类创建的猫对象都有一个name的属性
self.name = 'Tom'
def eat(self):
print('%s爱吃鱼' % self.name)
# 使用类名()创建对象时,会自动调用初始化方法
tom = Cat()
# print(tom.name)
tom.eat()
# 输出
这是一个初始化方法
Tom爱吃鱼
2.6.4 改造初始化方法----初始化的同时设置初始值
- 在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对__init__方法进行改造
- 把希望设置的属性值,定义成__init__方法的参数
- 把方法内部使用 self.属性 = 形参 接收外部传递的参数
- 在创建对象时,使用 类名(属性1,属性2...)调用
class Cat:
def __init__(self, new_name):
# 定义用Cat类创建的猫对象都有一个name的属性
# self.name = 'Tom'
self.name = new_name
def eat(self):
print('%s爱吃鱼' % self.name)
# 使用类名()创建对象时,会自动调用初始化方法
tom = Cat('Tom')
print(tom.name)
# tom.eat()
lazy_cat = Cat('大懒猫')
lazy_cat.eat()
# 输出
Tom
大懒猫爱吃鱼
2.7 内置方法和属性
- __del__ 方法:对象被从内存中销毁前,会被自动调用
- __str__ 方法:返回对象的描述信息,print函数输出使用
2.7.1 __del__ 方法
- 在Python中
- 当使用类名()创建对象时,为对象分配完空间后,自动调用__init__方法
- 当一个对象被从内存中销毁前,会自动调用__del__方法
- 生命周期
- 一个对象从调用类名()创建,生命周期开始
- 一个对象的__del__方法一旦被调用,生命周期结束
class Cat:
def __init__(self, name):
self.name = name
print('%s 来了' % self.name)
def __del__(self):
print('%s 走了' % self.name)
# tom是一个全局变量
tom = Cat('Tom')
print(tom.name)
# del关键字可以删除一个对象
del tom
print('-' * 50)
# 输出
Tom 来了
Tom
Tom 走了 # tom是一个全局变量,若不使用del关键字,分割线会在__del__方法被调用之前输出
--------------------------------
2.7.2 __str__方法
- 在python中,使用print输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
- 如果在开发中,希望使用print输出对象变量时,能够打印自定义内容,就可以利用__str__这个内置方法了
- 注意:__str__方法必须返回一个字符串
class Cat:
def __init__(self, name):
self.name = name
print('%s 来了' % self.name)
def __del__(self):
print('%s 走了' % self.name)
def __str__(self):
# 必须返回一个字符串
return '我是小猫[%s]' % self.name
# tom是一个全局变量
tom = Cat('Tom')
print(tom)
# 输出
Tom 来了
我是小猫[Tom]
Tom 走了
面向对象封装案例----01
class Person:
def __init__(self, name, weight):
# self.属性 = 形参
self.name = name
self.weight = weight
def __str__(self):
# 必须返回字符串
return '我的名字叫%s 体重是%.2f公斤' % (self.name, self.weight)
def run(self):
print('%s爱跑步,跑步锻炼身体' % self.name)
self.weight -=0.5
def eat(self):
print('%s是吃货,吃完这顿再减肥' % self.name)
self.weight += 1
xiaoming = Person('小明', 75.0)
xiaoming.run()
xiaoming.eat()
print(xiaoming)
# 输出
小明爱跑步,跑步锻炼身体
小明是吃货,吃完这顿再减肥
我的名字叫小明 体重是75.50公斤
案例扩展
class Person:
def __init__(self, name, weight):
# self.属性 = 形参
self.name = name
self.weight = weight
def __str__(self):
# 必须返回字符串
return '我的名字叫%s 体重是%.2f公斤' % (self.name, self.weight)
def run(self):
print('%s爱跑步,跑步锻炼身体' % self.name)
self.weight -=0.5
def eat(self):
print('%s是吃货,吃完这顿再减肥' % self.name)
self.weight += 1
xiaoming = Person('小明', 75.0)
xiaoming.run()
xiaoming.eat()
print(xiaoming)
# 小美
xiaomei = Person('小美', 45)
xiaomei.eat()
xiaomei.run()
print(xiaomei)
# 输出
小明爱跑步,跑步锻炼身体
小明是吃货,吃完这顿再减肥
我的名字叫小明 体重是75.50公斤
小美是吃货,吃完这顿再减肥
小美爱跑步,跑步锻炼身体
我的名字叫小美 体重是45.50公斤
- 在对象的方法内部,是可以直接访问对象的属性的
- 同一个类创建的多个对象之间,属性互不干扰
面向对象封装案例----02
class HouseItem:
def __init__(self, name, area):
self.name = name
self.area = area
def __str__(self):
return '[%s] 占地 %.2f' % (self.name, self.area)
class House:
def __init__(self, house_type, area):
self.house_type = house_type
self.area = area
# 剩余面积(初始剩余面积=房子的面积)
self.free_area = area
# 家具名称列表(初始家具列表为空)
self.item_list = []
def __str__(self):
# python能够自动的将一对括号内部的代码连接在一起
return ('户型:%s\n总面积:%.2f[剩余面积:%.2f]\n家具:%s'
% (self.house_type, self.area,
self.free_area, self.item_list))
def add_item(self, item):
print('要添加 %s' % item)
# 1.判断家具的面积
if item.area > self.free_area:
print('%s的面积太大了,无法添加' % item.name)
return
# 2.将家具的名称添加到列表中
self.item_list.append(item.name)
# 3.计算剩余面积
self.free_area -= item.area
# 1.创建家具
bed = HouseItem('席梦思', 40)
print(bed)
chest = HouseItem('衣柜', 2)
print(chest)
table = HouseItem('餐桌', 20)
print(table)
# 2.创建房子对象
my_home = House('两室一厅', 60)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)
# 输出
[席梦思] 占地 40.00
[衣柜] 占地 2.00
[餐桌] 占地 20.00
要添加 [席梦思] 占地 40.00
要添加 [衣柜] 占地 2.00
要添加 [餐桌] 占地 20.00
餐桌的面积太大了,无法添加
户型:两室一厅
总面积:60.00[剩余面积:18.00]
家具:['席梦思', '衣柜']
- 主程序只负责创建房子对象和家具对象
- 让房子对象调用add_item方法将家具添加到房子中
- 面积计算、剩余面积、家具列表等处理都被封装到房子类的内部
面向对象封装案例----03
- 一个对象的属性可以是另外一个类创建的对象
- 定义没有初始值的属性
- 在定义属性时,如果不知道设置什么初始值,可以设置为None
- None关键字表示什么都没有
- 表示一个空对象,没有方法和属性,是一个特殊的常量
- 可以将None赋值给任何一个变量
- 在定义属性时,如果不知道设置什么初始值,可以设置为None
class Gun:
def __init__(self, model):
# 1.枪的型号
self.model = model
# 2.子弹的数量
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
def shoot(self):
# 1.判断子弹数量
if self.bullet_count <= 0:
print('[%s]没有子弹了。。。' % self.model)
return
# 2.发射子弹
self.bullet_count -= 1
# 3.提示发射信息
print('[%s]突突突。。。[%s]' % (self.model, self.bullet_count))
class Soldier:
def __init__(self, name):
# 1.姓名
self.name = name
# 2.枪 --- 新兵没有枪
self.gun = None
def fire(self):
# 1.判断士兵是否有枪
# if self.gun == None:
if self.gun is None:
print('[%s]还没有枪' % self.name)
return
# 2.高喊口号
print('冲啊。。。[%s]' % self.name)
# 3.让枪装填子弹
self.gun.add_bullet(50)
# 4.让枪发射子弹
self.gun.shoot()
# 1.创建枪对象
ak47 = Gun('AK47')
# ak47.add_bullet(50)
# ak47.shoot()
# 2.创建许三多
xusanduo = Soldier('许三多')
xusanduo.gun = ak47
xusanduo.fire()
# print(xusanduo.gun)
# 输出
冲啊。。。[许三多]
[AK47]突突突。。。[49]
身份运算符
身份运算符用于比较两个对象的内存地址是否一致 —— 是否是对同一个对象的引用
- 在python中针对None比较时,建议使用is判断
运算符 | 描述 | 实例 |
is | is是判断两个标识符是不是引用同一个对象 | x is y,类似 id(x) == id(y) |
is not | is not是判断两个标识符是不是引用不同对象 | x is not y,类似 id(x) != id(y) |
- is 与 == 区别:
- is 用于判断两个变量引用对象是否为同一个
- == 用于判断引用变量的值是否相等
a = [1,2,3]
b = [1,2,3]
b is a
>>>False
b == a
>>>True
2.8 私有属性和私有方法
定义方式
- 在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法
伪私有属性和私有方法(科普)
提示:在日常开发中,不要使用这种方式,访问对象的私有属性或私有变量
python中,并没有真正意义的私有
- 在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到
- 处理方式:在名称前面加上 _类名 ==> _类名__名称
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)
print(xiaofang._Women__age)
# 伪私有方法,同样不允许在外界直接访问
# xiaofang.__secret()
xiaofang._Women__secret()
# 输出
18
小芳的年龄是18
2.9 继承
2.9.1 单继承
2.9.1.1 继承的概念、语法和特点
继承的概念:子类拥有父类的所有方法和属性
继承的语法:
class 类名(父类名):
pass
- 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
- 子类中应该根据职责,封装子类特有的属性和方法
class Animal:
def eat(self):
print('吃')
def drink(self):
print('喝')
def run(self):
print('跑')
def sleep(self):
print('睡')
class Dog(Animal):
# 子类拥有父类的所有属性和方法
def dark(self):
print('叫')
wangcai = Dog()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.dark()
继承的传递性:
- C类从B类继承,B类又从A类继承
- 那么C类就具有B类和A类的所有属性和方法
2.9.1.2 方法的重写
- 当父类方法实现不能满足子类需求时,可以对方法进行重写
重写父类方法有两种情况:
- 覆盖父类的方法
- 对父类方法进行扩展
1) 覆盖父类的方法
- 如果在开发中,父类的方法实现和子类的方法实现,完全不同
- 就可以使用覆盖的方式,在子类中重新编写父类的方法实现
具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现
重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
2) 对父类方法进行扩展
- 如果在开发中,子类的方法实现中包含父类的方法实现
- 父类原本封装的方法实现是子类方法的一部分
- 就可以使用扩展的方式
- 在子类中重写父类的方法
- 在需要的位置使用 super().父类方法 来调用父类方法的执行
- 代码其他的位置针对子类的需求,编写子类特有的代码实现
关于super
- 在python中 super 是一个特殊的类
- super() 就是使用super类创建出来的对象
- 最常使用的场景就是重写父类方法时,调用在父类中封装的方法实现
class 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()
# 3.增加其他子类的代码
print('&******')
xtq = XiaoTianQuan()
xtq.bark()
# 输出
叫的厉害
叫
&******
调用父类的另一种方式(知道)
父类名.方法(self)
- 不推荐使用,因为一旦父类发生变化,方法调用位置的类名同样需要修改
- 在开发中,父类名和super()两种方式不要混用
- 如果使用当前子类名调用方法,会形成递归调用,出现死循环
2.9.2 父类的私有属性和私有方法
- 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
- 子类对象可以通过父类的公有方法间接访问到私有属性或私有方法
- 私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问
- 私有属性、方法通常用于做一些内部的事情
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()
# 创建一个子类对象
b = B()
# print(b)
b.demo()
# 在外界访问父类的私有属性或调用私有方法
# print(b.num1)
# b.test()
# 在外界不能直接访问对象的私有属性或调用私有方法
# print(b.__num2)
# b.__test
# 输出
子类方法 100
父类的公有方法 200
私有方法 100 200
B的对象不能直接访问__num2属性
- B的对象不能直接访问__num2属性
- B的对象不能在demo方法内访问__num2属性
- B的对象可以在demo方法内,调用父类的test方法
- 父类的test方法内部,能够访问__num2属性和__test方法
2.9.3 多继承
概念:子类可以拥有多个父类,并且具有所有父类的属性和方法
语法:
class 子类名(父类名1, 父类名2...):
pass
注意事项
- 如果不同的父类中存在同名的属性或方法,应该尽量避免使用多继承
class A:
def test(self):
print('A --- test方法')
def demo(self):
print('A --- demo方法')
class B:
def test(self):
print('B --- test方法')
def demo(self):
print('B --- demo方法')
class C(A, B):
pass
# 创建子类对象
c = C()
c.test()
c.demo()
# 输出
A --- test方法
A --- demo方法
python中的MRO——方法搜索顺序
- python中针对类提供了一个内置属性 __mro__ 可以查看方法搜索顺序
- MRO是method resolution order,主要用于在多继承时判断方法、属性的调用路径
print(C.__mro__)
输出结果
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
- 在搜索方法时,是按照__mro__的输出结果从左至右的顺序查找的
新式类与旧时(经典)类
object是python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir函数查看
- 新式类:以object为基类的类,推荐使用
- 经典类:不以object为基类的类,不推荐使用
新式类和经典类在多继承时,会影响到方法的搜索顺序
在定义类时,如果没有父类,建议统一继承自object
class 类名(object):
pass
2.10 多态
不同的对象调用相同的方法,产生不同的执行结果
多态案例演练
class Dog:
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:
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)
# 输出
# 小明 和 旺财 快乐的玩耍。。。
# 旺财 蹦蹦跳跳的玩耍。。。。
小明 和 飞天旺财 快乐的玩耍。。。
飞天旺财 飞到天上去玩耍。。。
03. 类
3.1 实例
- 使用面向对象开发,第一步是设计类
- 使用 类名() 创建对象,创建对象的动作有两步:
- 在内存中为对象分配空间
- 调用初始化方法 __init__ 为对象初始化
- 对象创建后,内存中就有了一个对象的实实在在的存在——实例
因此,通常也会把:
- 创建出来的对象叫做类的实例
- 创建对象的动作叫做实例化
- 对象的属性叫做实例属性
- 对象调用的方法叫做实例方法
在程序执行时:
- 对象各自拥有自己的实例属性
- 调用对象方法,可以通过 self. :
- 访问自己的属性
- 调用自己的方法
结论
- 每一个对象都有自己独立的内存空间,保存各自不同的属性
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部
3.2 类是一个特殊的对象
Python中一切皆对象
- class AAA: 定义的类属于类对象
- obj1 = AAA() 属于实例对象
- 在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例
- 除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法
- 类属性
- 类方法
- 通过 类名. 的方式可以访问类的属性或者调用类的方法
3.3 类属性和实例属性
3.3.1 概念和使用
- 类属性就是给类对象中定义的属性
- 通常用来记录与这个类相关的特征
- 类属性不会用于记录具体对象的特征
示例
class Tool:
# 使用赋值语句,定义类属性,记录创建工具对象的总数
count = 0
def __init__(self, name):
self.name = name
# 让类属性的值+1
Tool.count += 1
# 1.创建工具对象
tool1 = Tool('斧头')
tool2 = Tool('榔头')
tool3 = Tool('水桶')
# 2.输出工具对象的总数
print(Tool.count) # 类名.类属性
# print('工具对象总数 %d' % tool3.count) # 对象.类属性
# 输出
3
# 工具对象总数 3
3.3.2 属性的获取机制(科普)
- 在python中属性的获取存在一个向上查找机制
- 因此,要访问类属性有两种方式:
- 类名.类属性
- 对象.类属性(不推荐)
- 如果使用 对象.类属性 = 值 赋值语句,只会给对象添加一个属性,而不会影响到类属性的值
3.4 类方法和静态方法
3.4.1 类方法
- 类属性 就是针对 类对象 定义的属性
- 使用赋值语句在 class 关键字下方可以定义 类属性
- 类属性 用于记录与这个类相关的特征
- 类方法 就是针对 类对象 定义的方法
- 在类方法内部可以直接访问 类属性 或者调用其他的 类方法
语法:
@classmethod
def 类方法名(cls):
pass
- 类方法需要用修饰器 @classmethod 来标识,告诉解释器这是一个类方法
- 类方法的第一个参数应该是 cls
- 由哪一个类调用的方法,方法内的cls就是哪一个类的引用
- 这个参数和实例方法的第一个参数self类似
- 提示 使用其他名称也可以,不过习惯使用cls
- 通过 类名. 调用类方法,调用方法时,不需要传递cls参数
- 在方法内部
- 可以通过 cls. 访问类的属性
- 也可以通过 cls. 调用其他的类方法
示例
class Tool:
# 使用赋值语句,定义类属性,记录创建工具对象的总数
count = 0
@classmethod
def show_tool_count(cls):
print('工具对象的数量 %d' % cls.count)
def __init__(self, name):
self.name = name
# 让类属性的值+1
Tool.count += 1
# 创建工具对象
tool1 = Tool('斧头')
tool2 = Tool('榔头')
# 调用类方法
Tool.show_tool_count()
# 输出
工具对象的数量 2
3.4.2 静态方法
- 在开发时,如果需要在类中封装一个方法,这个方法:
- 既不需要访问实例属性或者调用实例方法
- 也不需要访问类属性或者调用类方法
- 这个时候,可以把这个方法封装成一个静态方法
语法:
@staticmethod
def 静态方法名():
pass
- 静态方法需要用修饰器 @staticmethod 来标识,告诉解释器这是一个静态方法
- 通过 类名. 调用静态方法
class Dog:
@staticmethod
def run():
# 不访问实例属性/类属性,就可以把这个方法定义为静态方法
print('小狗要跑。。。')
# 通过 类名. 调用静态方法 ---不需要创建对象
Dog.run()
# 输出
小狗要跑。。。
3.5 方法综合案例
class Game:
# 历史最高分
top_score = 0
# 在初始化方法中定义实例属性
def __init__(self, player_name):
self.player_name = player_name
@staticmethod
def show_help():
print('帮助信息:让僵尸进入大门')
@classmethod
def show_top_score(cls):
print('历史记录 %d' % cls.top_score)
def start_game(self):
print('%s 开始游戏啦。。。' % self.player_name)
# 1.查看游戏的帮助信息
Game.show_help()
# 2.查看历史最高分
Game.show_top_score()
# 3.创建游戏对象
game = Game('小明')
game.start_game()
# 输出
帮助信息:让僵尸进入大门
历史记录 0
小明 开始游戏啦。。。
案例小结
- 实例方法——方法内部需要访问 实例属性
- 实例方法 内部可以使用 类名. 访问类属性
- 类方法——方法内部 只 需要访问 类属性
- 静态方法——方法内部,不需要访问 实例属性 和 类属性
- 方法内部既需要访问实例属性,又需要访问类属性,应该定义什么方法?
- 应该定义 实例方法
- 因为 类只有一个,在实例方法内部可以使用 类名. 访问类属性
04. 单例
- 单例设计模式
- 目的 —— 让 类 创建的对象,在系统中只有唯一的一个实例
- 每一次执行 类名() 返回的对象,内存地址是相同的
4.1 __new__ 方法
- 使用 类名() 创建对象时,python的解释器首先会调用__new__方法为对象分配空间
- __new__ 是一个由object基类提供的内置的静态方法,主要作用有两个:
- 在内存中为对象分配空间
- 返回对象的引用
- python的解释器获得对象的引用后,将引用作为第一个参数,传递给__init__方法
- 重写 __new__ 方法的代码非常固定!!
- 重写 __new__ 方法一定要 return super().__new__(cls)
- 否则python的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法
- 注意:__new__ 是一个静态方法,在调用时需要主动传递cls参数
class MusicPlayer:
def __new__(cls, *args, **kwargs):
# 1.创建对象时,new方法会被自动调用
print('创建对象,分配空间')
# 2.为对象分配空间
instance = super().__new__(cls)
# 3.返回对象的引用
return instance
def __init__(self):
print('播放器初始化')
# 创建播放器对象
player = MusicPlayer()
print(player)
# 输出
创建对象,分配空间
播放器初始化
<__main__.MusicPlayer object at 0x0000027A4AF76D90>
4.2 python中的单例
- 单例 —— 让 类 创建的对象,在系统中只有唯一的一个实例
- 定义一个类属性,初始值是None,用于记录单例对象的引用
- 重写 __new__ 方法
- 如果类属性 is None,调用父类方法分配空间,并在类属性中记录结果
- 返回类属性中记录的对象引用
class MusicPlayer:
# 记录第一个被创建对象的引用
instance = None
def __new__(cls, *args, **kwargs):
# 1.判断类属性是否是空对象
if cls.instance is None:
# 2.调用父类的方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
# 3.返回类属性保存的对象引用
return cls.instance
# 创建多个对象
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
# 输出
<__main__.MusicPlayer object at 0x000002441FE8C400>
<__main__.MusicPlayer object at 0x000002441FE8C400>
只执行一次初始化工作
- 在每次使用 类名() 创建对象时,python的解释器都会自动调用两个方法:
- __new__ 分配空间
- __init__ 对象初始化
- 之前对 __new__ 方法改造后,每次都会得到第一次被创建对象的引用
- 但是,初始化方法还会被再次调用
需求
- 让初始化动作只被执行一次
解决办法
- 定义一个类属性 init_flag 标记是否执行过初始化动作,初始值为 False
- 在 __init__ 方法中,判断 init_flag ,如果为 Flase 就执行初始化动作
- 然后将 init_flag 设置为 True
- 这样,再次自动调用 __init__ 方法时,初始化动作就不会被再次执行了
class MusicPlayer:
# 记录第一个被创建对象的引用
instance = None
# 记录是否执行过初始化动作
init_flag = False
def __new__(cls, *args, **kwargs):
# 1.判断类属性是否是空对象
if cls.instance is None:
# 2.调用父类的方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
# 3.返回类属性保存的对象引用
return cls.instance
def __init__(self):
# 1.判断是否执行过初始化动作
if MusicPlayer.init_flag:
return
# 2.如果没有执行过,再执行初始化动作
print('初始化播放器')
# 3.修改类属性的标记
MusicPlayer.init_flag = True
# 创建多个对象
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
# 输出
初始化播放器
<__main__.MusicPlayer object at 0x000002E1B7E2C400>
<__main__.MusicPlayer object at 0x000002E1B7E2C400>