最近看到一篇非常深奥的算法源码,发现它的底层逻辑全部都是由类实现的,而这种实现方式有非常大的好处,同样对于编程人员来讲,这种实现方式无疑增加了难度和挑战。没有受过系统训练的编程人员,常规的思路与编程习惯基本上都会是面向过程编程,很明显这种方式较为简单,但问题是这种思维方式,一旦遇到变换后其后期维护比较困难。好了转入正题,下面的内容也是自己在网上学习别人总结的资料,为方便后续学习与理解,全文内容属于转载【来源--知乎:纳西】,如对作者造成侵权,请及时联系我,我可及时删除,在此表示感谢。(大家可以跳转到原作者链接上进行系统学习)
1 面向对象(OOP)基本概念
1.1 过程和函数
- 过程是早期的一个编程概念
- 过程类似于函数,但只能执行,没有返回值
- 函数不仅能执行,还可以返回结果
1.2 面向过程和面向对象
1.2.1 面向过程
- 把某一个需求的所有步骤,从头到尾逐步实现
- 根据开发需求,将某些功能独立的代码封装成一个又一个函数
- 最后顺序地调用不同的函数,完成代码
特点:
- 注重步骤和过程,不注重职责分工
- 开发复杂项目,没有固定的套路,开发难度很大
之前的名片管理系统:
复杂的情况:一旦修改其中一个函数的参数,调用它的地方也要修改
只封装功能在函数中
函数之间会彼此调用,关系复杂
1.2.2 面向对象——谁来做
- 在完成某一个需求前,首先确定职责——要做的事情(方法)
- 根据职责确定不同的对象,但对象内部封装不同的方法(多个)
- 最后,顺序地让不同的对象调用不同的方法
特点:
- 注重对象和职责
不仅将功能封装在方法中,还将方法封装在对象中。
不同对象不会调用对方的方法,关系简单
2 类和对象
2.1 概念
1、类
类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
- 特征被称为 属性
- 行为被称为 方法
2、对象
对象是由类创建出来的一个具体存在,可以直接使用
由哪一个类创建出来的对象,就拥有哪一个类中定义的:属性、方法
在程序开发中,先有类,再有对象(类似于先有图纸,再有实物)
2.2 类和对象的关系
类只有一个,对象可以有多个
不同对象之间属性可能会各不相同
类中定义有什么属性和方法,对象也一样,不多不少
2.3 类的设计
在使用面向对象开发前,应该首先分析需求,确定程序中需要哪些类:
设计一个类,通常要满足三个要素:
- 类名:大驼峰命名法(例如
GapWords
),提炼需求中的名词 - 属性:这个类的特征
- 方法:这个类的行为
练习:
1、需求:
- 小明今年18岁,身高1.75,每天早上跑完步,会去吃东西
- 小美今年17岁,身高1.65,小美不跑步,小美喜欢吃东西
2、提炼:创建类
Person:name、age、height;run()、eat()
3 面向对象基本语法
3.1 dir()
变量、数据、函数都是对象
在python中可以用两个方法验证:
- 在
ipython
中,在标识符/数据后面输入.
,然后按下tab
键,可以查看对象内的属性和方法 - 使用内置函数
dir
传入标识符/数据,可以查看对象内所有属性及方法
def sum_number(num):
# 递归的出口
if num == 1: # 累加的最小数字
return 1
# 【假设sum_number(num-1)能够得到(1+2+...+num-1)】
temp = sum_number(num-1)
return temp + num
print(dir(sum_number))
__方法名__
格式的方法是python提供的内置方法/属性:
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | __new__ | 方法 | 创建对象时,会被自动调用 |
02 | __init__ | 方法 | 对象被初始化时,会被自动调用 |
03 | __del__ | 方法 | 对象被从内存中销毁前,会被自动调用 |
04 | __str__ | 方法 | 返回对象的描述信息,print函数输出使用 |
利用好dir
函数,很多方法不用死记硬背
3.2 定义简单的类(只包含方法)
3.2.1 定义类
class 类名:
def 方法1(self, 参数列表):
pass
def 方法1(self, 参数列表):
pass
方法定义的格式和之前类似,区别在于
第一个参数必须是self
3.2.2 创建对象
对象变量 = 类名()
3.2.3 练习——第一个面向对象程序
需求:小猫爱吃鱼,小猫要喝水
分析:Cat:eat()、 drink()
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
# 创建对象
tom = Cat()
tom.eat()
tom.drink()
引用:在python中使用类创建对象之后,tom变量中 记录的是对象在内存中的地址
使用
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
# 创建对象
tom = Cat()
print(tom) # <__main__.Cat object at 0x000001EAFAB264C0>
< main.Cat object at 0x000001EAFAB264C0>:
Cat类名,object对象,地址0x000001EAFAB264C0
意思:Cat对象在0x000001EAFAB264C0的地址
print(id(tom)) # 3049420584128
用id()
打印出来的地址是十进制的
%d %x:
%d:以十进制输出数字
%x:以十六进制输出数字
3.2.4 创建多个对象
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
# 创建对象:不是同一只猫
tom = Cat()
lazy_cat = Cat()
print(tom)
print(lazy_cat)
"""
<__main__.Cat object at 0x000002CD9CE874C0>
<__main__.Cat object at 0x000002CD9CE969A0>
"""
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
# 创建对象:是同一只猫
lazy_cat = Cat()
lazy_cat2 = lazy_cat
print(lazy_cat)
print(lazy_cat2)
"""
<__main__.Cat object at 0x00000208E64E74C0>
<__main__.Cat object at 0x00000208E64E74C0>
"""
3.3 在类的外部给对象增加属性
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
tom = Cat()
# 增加属性
tom.name = "Tom"
lazy_cat = Cat()
3.4 self
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
# 创建对象
tom = Cat()
tom.name = "Tom"
tom.drink()
tom.eat()
print(tom.name)
self和tom的地址是一样的,且属性相同
⏬
哪一个对象调用的方法,self就是哪一个对象的引用
class Cat:
def eat(self):
print("%s 爱吃鱼" % self.name)
def drink(self):
print("小猫要喝水")
# 创建对象
tom = Cat()
tom.name = "Tom"
tom.drink()
tom.eat() # Tom 爱吃鱼
print(tom.name)
在方法内部:
- 可以通过
self.
访问对象的属性 - 可以通过
self.
调用其他的对象方法
不建议在方法外部增加对象的属性:
# 会出错
class Cat:
def eat(self):
print("%s 爱吃鱼" % self.name)
# 创建对象
tom = Cat()
tom.eat()
tom.name = "Tom"
3.5 初始化方法
3.5.1 概念
当使用类名()
创建对象时,会自动执行以下操作:
- 为对象在内存分配空间——创建对象
- 为对象的属性设置初始值——初始化方法(init)
这个初始化方法就是__init__
方法__init__
方法是专门用来定义一个类具有哪些属性的方法
# 验证创建对象时,会自动调用初始化方法
class Cat:
def __init__(self):
print("这是一个初始化方法")
# 创建对象
tom = Cat()
"""
这是一个初始化方法
"""
3.5.2 在初始化方法内部定义属性
定义属性,在__init__
内部使用self.属性名 = 属性的初始值
定义属性之后,再使用类名()
创建对象,该对象就拥有这个属性
class Cat:
def __init__(self):
print("这是一个初始化方法")
self.name = "Tom"
# 创建对象
tom = Cat()
print(tom.name)
"""
这是一个初始化方法
Tom
"""
3.5.3 使用形参对初始化方法进行改造
之前的代码,name属性的值一直是"Tom"
要修改name,可以给__init__
方法传入一个形参
class Cat:
def __init__(self, new_name):
print("这是一个初始化方法")
self.name = new_name
# 创建对象
tom = Cat("Tom")
print(tom.name)
lazy_cat = Cat("大懒猫")
print(lazy_cat.name)
"""
这是一个初始化方法
Tom
这是一个初始化方法
大懒猫
"""
3.6 __del__
方法
一个对象被从内存中销毁前,会自动调用__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 = Cat("Tom")
print(tom.name)
lazy_cat = Cat("大懒猫")
print(lazy_cat.name)
print("-" * 50)
"""
Tom 来了
Tom
大懒猫 来了
大懒猫
--------------------------------------------------
Tom 去了
大懒猫 去了
"""
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 来了" % self.name)
def __del__(self):
print("%s 去了" % self.name)
# 创建对象
tom = Cat("Tom")
print(tom.name)
del tom # 删除对象
print("-" * 50)
"""
Tom 来了
Tom
Tom 去了
--------------------------------------------------
"""
3.7 __str__
方法
使用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)
print("-" * 50)
"""
Tom 来了
我是小猫[Tom]
--------------------------------------------------
Tom 去了
"""
4 面对对象的三大特性之一——封装
4.1 封装
封装——将 属性 和 方法 封装到一个抽象的类中
面向对象编程的第一步,将 属性和 方法封装到一个 抽象的类中
外界使用类创建 对象,然后让对象 调用方法
4.2 案例
4.2.1 小明爱跑步
小明体重75.0
公斤
小明每次跑步会减肥0.5
公斤
小明每次吃东西体重增加1
公斤
class Person:
def __init__(self, name, weight):
self.name = name
self.weight = weight
def __str__(self):
return "%s的体重是%.2f 公斤" % (self.name, self.weight)
def eat(self):
print("吃东西")
self.weight += 1.0
def run(self):
print("跑步")
self.weight -= 0.5
xiaoMing = Person("小明", 76.0)
print(xiaoMing)
xiaoMing.eat()
print(xiaoMing)
xiaoMing.run()
print(xiaoMing)
"""
小明的体重是76.00 公斤
吃东西
小明的体重是77.00 公斤
跑步
小明的体重是76.50 公斤
"""
4.2.2 小明爱跑步扩展——小美也爱跑步
class Person:
def __init__(self, name, weight):
self.name = name
self.weight = weight
def __str__(self):
return "%s的体重是%.2f 公斤" % (self.name, self.weight)
def eat(self):
print("吃东西")
self.weight += 1.0
def run(self):
print("跑步")
self.weight -= 0.5
xiaoMing = Person("小明", 76.0)
xiaoMei = Person("小美", 45.0)
print(xiaoMing)
print(xiaoMei)
4.2.3 摆放家具
- 房子有户型、总面积、家具名称列表
- 新房子没有任何的家具
2. 家具有名字和占地面积,其中
- bed 4平米
- chest 2平米
- table 1.5平米
3. 将以上三件家具添加到房子中
4. 打印房子时,要求输出户型、总面积、剩余面积、家具名称列表
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]平米,\n剩余面积[%.2f]平米,\n家具有[%s]"
% (self.house_type, self.area,
self.free_area, self.item_list))
def add_item(self, item):
print("要添加[%s]" % item) # 【输出的是HouseItem的__str__()方法定义的输出】
if item.area > self.free_area:
print("房子剩余面积不够,无法添加 %s " % item.name)
return
self.free_area = self.free_area - item.area
self.item_list.append(item.name)
# 1、创建家具
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
print(bed)
print(chest)
print(table)
# 2、创建房子对象
my_home = House("两室一厅", 70)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
# 3、输出房子
print("-" * 50)
print(my_home)
4.3 一个对象的属性可以是另一个类创建的对象
案例:士兵突击
- 士兵许三多有一把AK47
- 士兵可以开火
- 枪可以发射子弹
- 枪装填子弹——增加子弹数量
# - 士兵许三多有一把AK47
# - 士兵可以开火
# - 枪可以发射子弹
# - 枪装填子弹——增加子弹数量
class Gun:
def __init__(self, cartridge_name):
self.cartridge_name = cartridge_name
# 【子弹数量初始为0,不需要传递参数】
self.cartridge_num = 0
def __str__(self):
return "%s 中还有 %d 颗子弹" % (self.cartridge_name, self.cartridge_num)
def play(self):
if self.cartridge_num == 0:
print("子弹数量不够,无法开枪")
return
self.cartridge_num -= 1
print("【发射子弹】... %d" % self.cartridge_num)
def add(self, count):
self.cartridge_num += count
print("【装填子弹】... %d" % self.cartridge_num)
class Soldier:
def __init__(self, name):
self.name = name
# 新兵没有枪【可以用None赋值给属性】
self.gun = None
def __str__(self):
if self.gun is None:
return "士兵 [%s] 没有枪" % self.name
else:
return "士兵 [%s] 有一把 [%s]" % (self.name, self.gun.cartridge_name)
def shoot(self):
# 1、判断士兵有没有枪
if self.gun is None:
print("士兵[%s]没有枪..." % self.name)
return
# 2、高喊口号
print("【士兵开枪】")
# 3、让枪装填子弹
self.gun.add(50)
self.gun.play()
AK47 = Gun("AK47")
print(AK47)
xuSanDuo = Soldier("许三多")
print(xuSanDuo)
xuSanDuo.gun = AK47
print(xuSanDuo)
xuSanDuo.shoot()
xuSanDuo.shoot()
4.4 身份运算符 is、is not
用于比较两个对象的内存地址是否一致(是否是对同一个对象的引用)
运算符 | 描述 | 实例 |
---|---|---|
is | is是判断两个标识符是否引用同一个对象 | x is y类似id(x)==id(y) |
is not | 判断两个标识符引用不同对象 | x is not y类似id(x)!=id(y) |
is和==的区别:
==
判断变量的值
is
判断变量的地址
a = [1, 2, 3]
b = [1, 2]
b.append(3)
print("a的地址%d" % id(a))
print("b的地址%d" % id(b))
print(a == b) # True
print(a is b) # False
None:
对于None的比较,建议使用is
判断
4.5 私有属性和私有方法
4.5.1 应用场景
对象的某些属性或方法只希望在对象的内部被使用,而不希望在外部被访问到
4.5.2 定义方法
在属性名或者方法名前增加两个下划线
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() # 可以执行:私有属性在方法内部可以被访问
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() # 报错
4.5.3 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._Women__age) # 18
xiaofang._Women__secret() # 小芳 的年龄是 18
在开发中,不要使用这种方法去访问对象的私有属性或私有方法(既然设置为隐私,就不要访问)
5 面向对象的三大特性之一——继承
5.1 概念
继承:实现代码的重用
子类继承自父类,可以使用父类所有的属性和方法
子类中应该根据职责,封装子类特有的属性和方法
5.2 语法
class 类名(父类名):
pass
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("汪汪叫")
wangcai = Dog()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
5.3 专业术语
Dog
类是Animal
类的子类,Animal
类是Dog
类的父类,Dog
类从Animal
类继承
Dog
类是Animal
类的派生类,Animal
类是Dog
类的基类,Dog
类从Animal
类派生
5.4 继承的传递性
C
类从B
类继承,B
类从A
类继承。那么C
类具有B
类和A
类的所有属性和方法
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 XiaoTian(Dog):
def fly(self):
print("哮天犬会飞")
wangcai = XiaoTian()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
wangcai.fly()
可以看出Animal类的被Dog和XiaoTian继承
5.5 注意事项
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 Cat(Animal):
def bark(self):
print("喵喵叫")
class XiaoTian(Dog):
def fly(self):
print("哮天犬会飞")
wangcai = XiaoTian()
wangcai.bark() # 汪汪叫
# XiaoTian类不能使用Cat类的方法和属性,他们没有继承关系
XiaoTian类不能使用Cat类的方法和属性,他们没有继承关系
5.6 方法的重写
5.6.1 覆盖父类的方法
当父类中的方法不能满足子类需求时,可以对方法进行重写
实现方法:在子类中定义一个同名方法
class Dog(Animal):
def bark(self):
print("汪汪叫")
class XiaoTian(Dog):
# 方法重写
def bark(self):
print("嗷嗷嗷~")
def fly(self):
print("哮天犬会飞")
wangcai = XiaoTian()
wangcai.bark() # 嗷嗷嗷~
5.6.2 对父类方法进行扩展
方法:
- 在子类重写父类方法
- 在需要的位置使用
super().父类方法
来调用父类方法
class Dog(Animal):
def bark(self):
print("汪汪叫")
class XiaoTian(Dog):
# 方法重写
def bark(self):
# 1.针对子类特有的需求,编写代码
print("嗷嗷嗷~")
# 2.使用super().调用父类中的方法
super().bark()
# 3.增加其他代码
print("吼吼吼~")
def fly(self):
print("哮天犬会飞")
wangcai = XiaoTian()
wangcai.bark()
"""
嗷嗷嗷~
汪汪叫
吼吼吼~
"""
super:
- super是一个特殊的类
super()
是使用super类创建出来的对象- 最常使用的场景是重写父类方法时,调用在父类中封装的方法
调用父类方法的另一种方式:
在python2.x
时,使用方法:
父类名.方法(self)
在python3.x
中不推荐使用(能用),因为一旦父类发生变化,调用位置的父类名同样需要修改
class Dog(Animal):
def bark(self):
print("汪汪叫")
class XiaoTian(Dog):
# 方法重写
def bark(self):
# 1.针对子类特有的需求,编写代码
print("嗷嗷嗷~")
# 2.使用super().调用父类中的方法
Dog.bark(self)
# 3.增加其他代码
print("吼吼吼~")
def fly(self):
print("哮天犬会飞")
wangcai = XiaoTian()
wangcai.bark()
"""
嗷嗷嗷~
汪汪叫
吼吼吼~
"""
5.7 父类的私有属性和私有方法
- 子类对象 不能在自己的方法内部,直接访问父类的私有属性或私有方法
- 子类对象 可以通过父类的公有方法 间接访问到私有属性或私有方法
class A:
def __init__(self):
self.__name = "A"
def __test1(self):
print("父类的私有方法")
def test(self):
print("父类的公有方法:")
print("父类中的私有属性 %s" % self.__name)
self.__test1()
class B(A):
pass
b = B()
b.test()
"""
父类的公有方法:
父类中的私有属性 A
父类的私有方法
"""
5.8 多继承
5.8.1 语法
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()
5.8.2 注意事项
不同的父类中有同名的方法,应该避免使用多继承
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方法
"""
5.8.3 MRO——方法搜索顺序
python针对类提供了一个内置属性__mro__
,可以查看方法搜索顺序
主要用于多继承时判断方法、属性的调用路径
语法:
对象.__mro__
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类对象调用方法的顺序
print(C.__mro__)
"""
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
顺序:C-A-B-object 【object是所有类的基类】
"""
5.8.4 新式类、旧式(经典)类
object
是python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir
函数查看
新式类:以object为基类的类
经典类:不以object为基类的类,不推荐使用
在python3.x
中,默认object
是基类
在python2.x
中,如果没有指定父类,则不会以object作为基类
为了保证代码在python2.x
和python3.x
中都可以运行,在定义类时,如果没有父类,建议统一继承自object:
class 类名(object):
pass
6 面向对象的三大特性之一——多态
多态:不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
前提:继承、重写父类方法
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()
wangCai = Dog("旺财")
xiaoMing = Person("小明")
xiaoMing.game_with_dog(wangCai)
"""
小明 和 旺财 快乐地玩耍
旺财 蹦蹦跳跳地玩耍
"""
wangCai = XiaoTianDog("飞天旺财")
xiaoMing = Person("小明")
xiaoMing.game_with_dog(wangCai)
"""
小明 和 飞天旺财 快乐地玩耍
飞天旺财 飞到天上玩耍
"""
7 类属性
7.1 术语
- 使用面向对象开发,第一步是设计类
- 使用类名()创建对象,创建对象有两步:
1)在内存为对象分配空间
2)调用初始化方法__init__
为对象初始化 - 对象创建后,内存中就有了一个对象的存在——实例
创建了三个对象,它们的内存位置不同,保存各自不同的属性
对象的方法只保存在一个位置,不同对象使用方法时,通过传递参数调用
因此:
- 创建出来的对象叫做类的实例
- 创建对象的动作叫做实例化
- 对象的属性叫做实例属性
- 对象调用的方法叫做实例方法
7.2 类是一个特殊的对象
python中一切皆对象,
class AAA:
定义的类属于 类对象
obj1 = AAA()
属于 实例对象
类对象可以拥有自己的属性和方法:
- 类属性
- 类方法
通过
类名.
的方式可以访问类的属性或者调用类的方法
7.3 类属性和实例属性
- 类属性就是给类对象中定义的属性
- 通常用来记录与这个类相关的特征
- 类属性不会用于记录具体对象的特征
语法:
使用赋值语句定义
使用类.类属性
调用
示例:
- 定义一个工具类
- 每件工具都有自己的name
- 需求——知道使用这个类,创建了多少个工具对象
class Tool(object):
# 定义类属性
count = 0
def __init__(self, name):
self.name = name
# 让类属性的值+1
Tool.count += 1
tool1 = Tool("斧头")
print(Tool.count)
tool2 = Tool("榔头")
print(Tool.count)
tool3 = Tool("水桶")
print(Tool.count)
属性的获取——向上查找机制
在python
中属性的获取存在一个向上查找机制
因此,要访问类属性有两种方式:
- 类名.类属性
- 对象.类属性(不推荐:读取时没有问题;设置值时不可以)
class Tool(object):
# 定义类属性
count = 0
def __init__(self, name):
self.name = name
# 让类属性的值+1
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("水桶")
# 方式一
print(Tool.count)
# 方式二
print(tool1.count)
print(tool3.count)
注意:
- 使用
对象.类属性 = 值
,只会给对象添加属性,不会影响到类属性的值 tool3.count = 100
在tool3内存空间查找是否有count属性,没有就添加;有就修改值
class Tool(object):
# 定义类属性
count = 0
def __init__(self, name):
self.name = name
# 让类属性的值+1
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("水桶")
tool1.count = 100
# 方式一
print(Tool.count) # 3
# 方式二
print(tool1.count) # 100
print(tool3.count) # 3
8 类方法
访问到类属性
8.1 语法
@classmethod
def 类方法名(cls):
pass
需要用 修饰器@classmethod
来标识, 告诉解释器这是一个类方法
类方法的 第一个参数应该是cls
- 由哪一个类调用的方法,
cls
就是哪一个类的引用 - 和实例方法中的
self
类似 - 提示:使用其他名称也可,不过习惯用
cls
8.2 案例
- 定义一个工具类
- 每件工具都有自己的name
- 需求——在类封装一个
show_tool_count
的类方法,输出使用当前这个类创建的对象个数
class Tool(object):
# 定义类属性
count = 0
@classmethod
def show_tool_count(cls):
print("工具对象的个数 %d" % cls.count) # 使用【cls.count】
def __init__(self, name):
self.name = name
# 让类属性的值+1
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("水桶")
Tool.show_tool_count()
9 静态方法
9.1 应用场景
- 既不需要访问实例属性或者调用实例方法
- 也不需要访问类属性或者调用类方法
9.2 语法
@staticmethod
def 静态方法名(): # 不需要self
pass
需要修饰器@staticmethod
告诉解释器,这是一个静态方法
通过 类名. 调用静态方法
class Dog(object):
@staticmethod
def run():
print("小狗要跑")
# 不创建对象,直接调用静态方法
Dog.run()
9.3 案例
- 设计一个Game类
- 属性:
- 定义一个类属性
top_score
记录游戏的历史最高分 - 定义一个实例属性
player_name
记录当前游戏的玩家姓名
3. 方法:
-
- 静态方法
show_help
显示游戏帮助信息 - 类方法
show_top_score
显示历史最高分 - 实例方法
start_game
开始当前玩家的游戏
- 静态方法
4. 主程序步骤
-
- 查看帮助信息
- 查看历史最高分
- 创建游戏对象,开始游戏
class Game(object):
# 历史最高分
top_score = 0
def __init__(self, name):
# 玩家姓名
self.player_name = 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)
# - 查看帮助信息
Game.show_help()
# - 查看历史最高分
Game.show_top_score()
# - 创建游戏对象,开始游戏
xiaoMing = Game("小明")
xiaoMing.start_game()
小结
- 实例方法——方法内部需要访问 实例属性
- 实例方法内部可以使用
类名.
访问类属性
2. 类方法——方法内部只需要访问 类属性
3. 静态方法——方法内部不需要访问类属性和实例属性