一、概念
1.1面向对象的设计思想
面向对象是基于万物皆对象这个哲学观点。在Python中,一切皆对象
举例说明:
案例一:我要吃大盘鸡
面向过程 面向对象
1.自己去买菜 1.委托一个会砍价的人帮忙去买菜
2.自己择菜 2.委托一个临时工帮忙择菜
3.自己做菜 3.委托一个厨师帮忙做菜
4.自己开始吃 4.自己开始吃
案例二:小明是一个电脑小白,想要配一台电脑,买完零件后需要运到家里,组装完成后打开电脑玩游戏
面向过程 面向对象
1.小明补充电脑知识 1.委托一个懂电脑的朋友(老王)去帮忙买零件
2.小明去买零件 2.委托一个能跑腿的人将零件运送到家里
3.小明把零件带回家里 3.委托一个会组装电脑的人帮小明组装电脑
4.小明组装电脑 4.小明自己打开电脑,开始玩游戏
5.小明开机玩电脑
1.2 面向过程和面向对象
1.2.1 面向过程
- 在生活案例中:
- 一种看待问题的思维方式,在思考问题的时候,着眼于问题是怎样一步一步解决的,然后亲力亲为的去解决问题
- 在程序中:
- 代码从上而下顺序执行
- 每一模块内部均是由顺序、选择和循环三种基本结构组成
- 程序流程在写程序时就已决定
1.2.2 面向对象
在生活案例中:
- 也是一种看待问题的思维方式,着眼于找到一个具有特殊功能的具体个体,然后委托这个个体去做某件事情,我们把这个个体就叫做对象,一切皆对象
- 是一种更符合人类思考习惯的思想(懒人思想),可以将复杂的事情简单化,将程序员从执行者角度转换成了指挥者角度
在程序中:
把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象- 对同类对象抽象出其共性,形成类
- 类中的大多数数据,只能用本类的方法进行处理
- 程序流程由用户在使用中决定
- 使用面向对象进行开发,先要去找具有所需功能的对象,如果该对象不存在,那么创建一个具有该功能的对象
注:面向对象只是一种思想,并不是一门编程语言,也不会绑定编程语言
1.2.3 面向过程和面向对象的优缺点
- 面向过程:
- 优点:性能比面向对象高,比如单片机、嵌入式开发等一般采用面向过程开发,因为性能是最重要的因素
- 缺点:没有面向对象易维护,易复用,易扩展,开销比较大,比较消耗资源
- 面向对象:
- 优点:易维护,易复用,易扩展,由于面向对象有封装,继承,多态的特性,可以设计出低耦合的系统,使得系统更加灵活,更加易于维护
- 缺点:性能比面向过程低
注:使用面向对象的思维解决问题,核心:类和对象
二、类和对象
2.1 概念
类:一个具有特殊功能的实体的集合,是抽象的概念
对象:在一个类中,一个具有特殊功能的实体,能够帮忙解决特定的问题(对象也被称为实例),是具体的存在
两者之间的关系:类用于描述某一类对象的共同特征,而对象则是类的具体存在
先有对象还是先有类?
- 先有对象,再有类:将多个具有共同特征的对象,提取出来一个类
- 先有类,再有对象:定义类,通过类创建对象,在代码中常用
举例:
类 对象
人 张三、李四、王麻子、杨阳。。。
SuperHero 蝙蝠侠、蜘蛛侠、美国队长。。。
快递 顺丰、圆通、申通、韵达。。。
帮助理解:类也是一种数据类型,只不过是自定义的,跟所学过int
、str
、bool
等类似。用类实例化对象相当于定义一个类的变量
num = 10
print(type(num)) # <class 'int'>
e = ValueError() # 类实例化对象 / 定义一个类的变量
print(type(e)) # <class 'ValueError'>
2.2 类的定义
- 语法
class 类名():
类体
说明
- Python中使用
class
关键字定义类
# 1.定义一个空类 class MyClass1(): pass class MyClass2(): pass
- 类名只要是一个合法的标识符即可,但是要求:遵循大驼峰命名法 ,如:
KeyError
,ValueError
,NameError
,IndexError
……. - 尽量使用单个或多个有意义的单词连接而成
- 通过缩进来体现类体的存在
- 类体一般包含两部分内容:对类的特征描述(变量)和行为描述(函数)
# 2.定义非空类 class MyClass3(): # 类体 # a.对类的特征描述:变量 num = 10 name = 'zhangsan' # b.对类的行为描述:函数 def show(self): print("showing") def func1(self): print("11111")
- Python中使用
总结
- 类的定义和函数的定义类似,但是,一个函数定义完毕之后,只有调用才会执行其中的代码;类定义完毕之后,其中的代码立马会被加载
- 同一个py文件中可以定义多个类,但是在实际项目开发中,由于每个类实现的代码可能较为复杂,类的定义建议一个py文件一个类(创建一个包,一个模块中定义一个类)
- 当一个类定义完成,类中的内容被称为类体,又被称为类的成员,当类被加载的时候,类中的成员也会被加载
- 和函数相同,类也会引入新的作用域,所以在类中定义的变量或函数,在类的外面无法直接访问
# 定义函数 def check(): print("函数————开始") print("函数————结束") # 定义类 class MyClass3(): print("类————开始") # 类体 # a.对类的特征描述:变量 num = 10 name = 'zhangsan' # b.对类的行为描述:函数 def show(self): print("showing") print("类————开始") print(num) # NameError: name 'num' is not defined.
**结果:**类中的内容被打印出来,而函数没有;并且类中定义的变量在外面无法访问
2.3 对象的创建
对象又叫实例,所以对象的创建又可以叫:创建对象/实例化对象/类的实例化
2.3.1 类中未定义构造函数
构造函数之一:__init__
语法:变量 = 类名()
# 1.未定义构造函数:__init__
# 定义类
class Person():
pass
# 语法:变量 = 类名()
# 创建对象
p1 = Person()
print(p1) # <__main__.Person object at 0x000001D2AD83FC50>
p2 = Person()
print(p2) # <__main__.Person object at 0x000001D2B0D78350>
解释:打印一个自定义类创建的对象,默认情况下,打印的是该对象在内存空间的地址<main.Person object at 0x10ec17400>
总结:
a.创建对象的过程,本质上就是定义变量的过程,该变量中存储的是创建出来的对象 b.打印一个自定义类创建的对象,默认情况下,打印的是该对象在内存空间的地址<__main__.Person object at 0x10ec17400> c.一个普通类,可以创建无数个对象,每个对象在内存空间中拥有独立的地址 d.类名() 表示创建对象,但是该代码每执行一次,则表示创建一个新的对象
2.3.2 类中定义构造函数
语法:变量 = 类名(xxx,xxx...)
,说明:xxx,xxx...
类似于函数中的传参(实参)
class Animal():
def __init__(self):
print("init-----", id(self)) # init----- 2197353966096
a1 = Animal()
print("a1:", id(a1)) # a1: 2197353966096
根据打印结果可知,在init
函数中的self
为初始化的参数
- 构造函数
__init__
- 构造函数之一:
__init__
,表示初始化,给对象初始化 - 形如
__xxx__
命名的函数,在Python中,成为魔术函数(魔术方法),此类函数无序手动调用,都会在特定的场景下自动调用 __init__
:当我们创建对象的时候,会自动调用__init__
函数self
:当前对象。无需传参,当创建对象的时候,会将当前创建的对象,自动传参给self
。
- 构造函数之一:
类中定义构造函数,一般用于给当前对象进行特征的描述
语法:对象.变量 = 值
创建对象的时候,需不需要传参,传几个参数,一定要和__init__
函数匹配
class Animal():
def __init__(self, name, age, kind):
print("init-----", id(self), name, age, kind)
# 语法:对象.变量 = 值,表示给当前对象进行特征的描述
self.name = name
self.age = age
self.kind = kind
# 注意:创建对象的时候,需不需要传参,传几个参数,一定要和__init__函数匹配
a1 = Animal("小白", 3, "猫")
print("a1:", id(a1))
print(a1.name, a1.age, a1.kind)
a2 = Animal('旺财',5,'田园犬')
# print('a2:',id(a2))
print(a2.name,a2.age,a2.kind)
使用__init__
函数的好处就是提高代码的复用性,如果不使用__init__
,创建多个对象时
class Animal():
pass
a1 = Animal()
a1.name = '小白'
a1.age = 3
a1.kind = '猫'
print(a1.name,a1.age,a1.kind)
a2 = Animal()
a2.name = '旺财'
a2.age = 5
a2.kind = '田园犬'
print(a2.name,a2.age,a2.kind)
代码需要重复的编写
2.4 类的设计
只需要关心3个要素
- 事物名称(类名)
- 例:人类(Person)
- 特征(变量)
- 例:身高(height)、年龄(age)————> 名词 ————>变量
- 行为(函数)
- 例:跑(run)、打架(fight)—————> 动词 ————> 函数
注:初期学习,通过提炼动名词进行类的提取
三、类中的成员
3.1 变量/属性
类中的变量也可以叫属性,分为类属性(类的字段)和实例属性(对象属性,对象的字段)
3.1.1 总结
类属性【类的字段】和实例属性【对象属性,对象的字段】的区别
- 定义位置不同
- 类属性直接定义在类中,只要是动态绑定的属性都是实例属性【在
__init__
中或在类的外面直接动态绑定定义】
- 类属性直接定义在类中,只要是动态绑定的属性都是实例属性【在
- 访问方式不同
- 类属性可以通过类名或对象访问,而实例属性只能通过对象访问
- 访问优先级不同
- 当类属性和实例属性重名时,通过对象访问,优先访问的是实例属性
- 在内存中出现的时机不同
- 类属性优先于实例属性出现在内存中,类属性随着类的加载而出现,实例属性是对象创建完毕之后才会出现
- 使用场景不同
- 类属性用于表示多个对象共享的数据,实例属性表示每个对象特有的数据
3.1.2 代码解析
3.1.2.1 定义位置
class Person():
# 1.定义的位置不同
# 类属性:直接定义在类中
place = "地球"
# 实例属性体现形式一:只要是通过语法:对象.属性 = 值 定义的属性
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("小明", 10)
# 实例属性体现形式二
p1.hobby = "跳舞"
3.1.2.2 访问方式
基于上诉的类,进行对象访问
# 类属性可以通过类名或者对象访问
# 通过类名访问
print(Person.place)
# 通过对象访问
print(p1.place)
# 实例属性只能通过对象访问
# 通过对象访问
print(p1.name, p1.hobby)
# 通过类名访问,报错
# print(Person.name) # AttributeError: type object 'Person' has no attribute 'name'
3.1.2.3 访问优先级
最后的结果为 11
,为实例属性
# 3.访问优先级不同:当类属性和实例属性重名时,通过对象访问,优先访问的是实例属性
class Person():
place = "地球"
num = 100
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("小明", 10)
p1.hobby = "跳舞"
p1.num = 11
print(p1.num) # 11
3.1.2.4 在内存中出现的时机
在写代码时,一般都是先定义类,然后在编写对象;
所以当程序运行时,先加载类,即类属性随着类的加载而出现;后加载对象,即实例属性是对象创建完毕之后才会出现
3.1.2.5 使用场景不同
不同对象打印类属性,为同一个值
不同对象打印实例属性,为不同的值
修改其中的一个对象的实例属性,其他对象的实例属性不受印象,即分别表示不同的内存地址,相互之间不影响
修改类属性,所以所有对象的类属性都发生更改,即示同一个地址,如果类属性的值发生修改,则都会更改
class Person():
place = "地球"
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("小明", 10)
p2 = Person('小花', 12)
p3 = Person('张三', 11)
# 打印类属性,为同一个值
print(p1.place, p2.place, p3.place)
# 打印实例属性,为不同的值
print(p1.name, p2.name, p3.name)
# 修改其中的一个对象的实例属性,其他对象的实例属性不受印象,即分别表示不同的内存地址,相互之间不影响
p1.name = 'Jack'
print(p1.name, p2.name, p3.name) # 分别表示不同的内存地址,相互之间不影响
# 修改类属性,所以所有对象的类属性都发生更改,即示同一个地址,如果类属性的值发生修改,则都会更改
Person.place = '火星'
print(p1.place, p2.place, p3.place) # 表示同一个地址,如果类属性的值发生修改,则都会更改
3.2 动态绑定属性
3.2.1 动态绑定属性
给对象动态绑定属性,语法:对象.属性 = 值
默认情况下,可以给一个对象绑定任意名称、任意数量的属性,即没有限制
如:下面的代码中给对象p1
,绑定了5个属性:name
、age
、hobby
、score
、zyx
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("小明", 10)
p1.hobby = "跳舞"
p1.score = 100
print(p1.name, p1.age, p1.hobby, p1.score)
p1.zyx = 33
3.2.2 限制对象属性的动态绑定
在实际应用中,在一些场景里面,需要对对象属性的动态绑定进行限制。
语法:在类中定义__slots__ = ('属性名1','属性名2'...)
,等号右边为元组,即创建的对象属性只能为元组中的元素
注意事项:注意元组的写法,当元组中的元素只有一个的时候,不能忘记,
如:下面的代码中给对象p1
,进行了限制对象属性的动态绑定,属性只能为:name
、age
、hobby
、score
。此时在定义对象属性zyx
时,报错
# b.限制对象属性的动态绑定
class Person:
# 限制对象属性的动态绑定
__slots__ = ("name", "age", "hobby", "score")
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("小明", 10)
p1.hobby = "跳舞"
p1.score = 100
print(p1.name, p1.age, p1.hobby, p1.score)
# p1.zyx = 33 # AttributeError: 'Person' object has no attribute 'zyx'
3.3 类中的函数/方法
类中的函数也可以成为方法,并且有三种类型:实例方法,类方法和静态方法
3.3.1 总结
实例方法,类方法和静态方法的区别和联系
不同点:
- 是否有装饰器装饰
- 实例方法无需装饰器装饰,类方法需要使用**@classmethod装饰,静态方法需要使用@staticmethod**装饰
- 形参不同
- 实例方法第一个参数必须是
self
,类方法的第一个参数是cls
,静态方法的参数没有要求
- 实例方法第一个参数必须是
- 调用方式不同
- 实例方法只能通过对象调用,类方法和静态方法可以通过类名或者对象调用
- 使用场景不同
- 如果要封装一个工具类,可以使用静态方法或类方法
- 在实际项目开发中,使用实例方法较多
相同点:
- 可以使用默认参数,关键字参数,不定长参数
- 可以使用返回值
3.3.2 代码解析
3.3.2.1 装饰器、形参角度
类方法需要使用@classmethod
装饰,第一个参数是cls
,cls
:表示当前类,如果需要自定义参数,添加到cls
后面即可
实例方法无需装饰器装饰,第一个参数必须是self
,self
:表示当前对象,如果需要自定义参数,添加到self
后面即可
静态方法需要使用@staticmethod
装饰,只需要定义自定义的参数即可
class Person():
# 1.特征:变量/属性
# 类属性
place = "地球"
def __init__(self, name, age):
# 实例属性
self.name = name
self.age = age
# 2.行为:函数/方法
# 类函数:需要用@classmethod装饰器
@classmethod
def check(cls): # cls:表示当前类,如果需要自定义参数,添加到cls后面即可
pass
# 实例函数,不需要装饰器
def show(self): # self:表示当前对象,如果需要自定义参数,添加到self后面即可
pass
# 静态函数,需要用@staticmethod装饰器
@staticmethod
def func(): # 只需要定义自定义的参数即可
pass
3.3.2.2 调用方式不同
实例方法只能通过对象调用,类方法和静态方法可以通过类名或者对象调用
若通过类名调用实例方法,报错
注意事项:
- 类函数中的变量
cls
和实例函数中的变量self
,都是系统自动完成传参的,无需手动传参- 如下面代码,在类函数中打印
cls
的值,与在类外面直接打印类Person
的值是一样的
- 如下面代码,在类函数中打印
- 类中的函数相互调用,都必须遵循通过
类名(即cls
或对象(即cls创建对象或self)
调用
class Person():
place = "地球"
def __init__(self, name, age):
self.name = name
self.age = age
# 类函数:需要用@classmethod装饰器
@classmethod
def check(cls):
print('类函数~~~~~check')
# 静态函数,需要用@staticmethod装饰器
@staticmethod
def func():
print('静态函数~~~~~func')
# 实例函数,不需要装饰器
def show(self):
print('实例函数~~~~~show')
# 创建一个对象
per = Person("小明", 20)
# 通过 类名 或者 对象 ,调用类函数
Person.check()
per.check()
# 通过 类名 或者 对象 ,调用静态函数
Person.func()
per.func()
# 只能通过 对象 调用实例函数
per.show()
# Person.show() # TypeError: Person.show() missing 1 required positional argument: 'self'
3.3.2.3 类中函数的相互调用
类中函数的相互调用,也需要遵守规则:实例方法只能通过对象调用,类方法和静态方法可以通过类名或者对象调用
类中的函数相互调用,都必须遵循通过类名(即cls)
或对象(即通过cls创建对象或self)
调用
- 在类函数中调用实例函数和静态函数
同样,在类中可以调用类属性
class Person():
place = "地球"
def __init__(self, name, age):
self.name = name
self.age = age
# 类函数:需要用@classmethod装饰器
@classmethod
def check(cls):
print('类函数~~~~~check')
# 创建对象
obj = cls("张三", 11)
# a1.在类函数中调用 实例函数 和 静态函数
obj.show()
# a2.在类函数中调用 静态函数
cls.func()
obj.show()
# 在类函数中访问类属性
print(obj.place) # 通过对象访问类属性
print(cls.place) # 通过类访问类属性
# 静态函数,需要用@staticmethod装饰器
@staticmethod
def func():
print('静态函数~~~~~func')
# 实例函数,不需要装饰器
def show(self):
print('实例函数~~~~~show')
# 调用类函数
Person.check()
输出结果
类函数~~~~~check
实例函数~~~~~show
静态函数~~~~~func
实例函数~~~~~show
地球
地球
- 在实例函数中调用类函数和静态函数
class Person():
place = "地球"
def __init__(self, name, age):
self.name = name
self.age = age
# 类函数:需要用@classmethod装饰器
@classmethod
def check(cls):
print('类函数~~~~~check')
# 静态函数,需要用@staticmethod装饰器
@staticmethod
def func():
print('静态函数~~~~~func')
# 实例函数,不需要装饰器
def show(self):
print('实例函数~~~~~show')
# 在实例对象中调用 类函数 和 静态函数
# 通过类名调用
Person.check()
Person.func()
# 通过对象调用
self.check()
self.func()
# 创建一个对象
per = Person("小明", 20)
# 只能通过 对象 调用实例函数
per.show()
运行结果
实例函数~~~~~show
类函数~~~~~check
静态函数~~~~~func
类函数~~~~~check
静态函数~~~~~func
- 在静态函数中不调用类方法和实例方法,无法获取准确的变量
3.3.2.4 使用场景
应用:定义工具类
工具类:目的是为了使用起来更加方便,所以在工具类中定义的函数一般是类函数或静态函数
因为调用的时候,无需创建对象就可以通过类名直接调用
# 定义一个加减乘除的类
class Number():
@staticmethod
def add(num1, num2):
return num1 + num2
@staticmethod
def sub(num1, num2):
return num1 - num2
@staticmethod
def mul(num1, num2):
return num1 * num2
@staticmethod
def div(num1, num2):
return num1 / num2
print(Number.add(10, 20))
print(Number.sub(10, 20))
print(Number.mul(10, 20))
print(Number.div(10, 20))