1 面向过程和面向对象
1.1 面向过程
(1)过程和函数
过程是早期的一个编程概念,类似于函数,只能执行,但是没有返回值;函数不仅能执行,还可以返回结果。
(2)使用
把完成某一个需求的所有步骤从头到尾逐步实现;根据开发需求,将某些功能独立的代码封装成一个又一个的函数;最后完成的代码,就是顺序地调用这些函数。
(3)特点
- 注重步骤与过程,不注重职责分工
- 如果需求复杂, 代码会变得很复杂
- 开发复杂项目,开发及维护难度大
1.2 面向对象
(1)使用
相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法:
- 在完成某一个需求前,首先确定职责
- 根据职责确定不同的对象,在对象内部封装不同的方法
- 最后完成的代码,就是顺序地让不同的对象调用不同的方法
(2)特点
- 注重对象和职责,不同的对象承担不同的职责
- 更加适合应对复杂的需求变化
2 类和对象
2.1 类
类是对一群具有相同特征或行为的事物的一个统称,是抽象的,不能直接使用。特征被称为属性,行为被称为方法。
类相当于制造飞机时的图纸,是一个模板,是负责创建对象的。
2.2 对象
对象是由类创建出来的一个具体存在,可以直接使用;由哪一个类创建出来的对象,就拥有在哪一个类中定义的属性和方法。
对象就相当于用图纸制造的飞机。在程序开发中,应先有类,再有对象。
2.3 类和对象的关系
- 先有类, 再有对象
- 类是对象的模板,通过类可以创建对象
- 类只有一个, 而对象可以有多个
- 类中有什么样的属性和方法,则对象中也会有什么样的属性和方法
- 对象之间的属性值和方法的实现可能有所不同
- 类是抽象的,不能直接使用,对象是具体存在,可以直接使用
2.4 类的设计
在使用面相对象开发前,应该首先分析需求,确定程序中需要包含哪些类。
在程序开发中,要设计一个类,通常需要满足以下三个要素:
- 类名:这类事物的名字, 满足大驼峰命名法
- 属性:这类事物具有什么样的特征
- 方法:这类事物具有什么样的行为
3 面向对象基础语法
3.1 dir 内置函数
是python中内置的函数,使用内置函数 dir (标识符 / 数据),可以用来查看变量或对象中所有的属性和方法。
3.2 简单类的定义(只包含方法)
语法:
class 类名:
def ⽅法1(self, 参数列表):
pass
def ⽅法2(self, 参数列表):
pass
3.2.1 方法和函数的区别
-
相同点:都是封装代码的整体, 都是实现某个功能的小工具
-
不同点:
① 定义的位置不同;函数定义在类的外部,方法定义在类的内部。
② 参数不同;函数没有self参数,方法有self参数。
③ 调用的方式不同;函数调用:函数名(参数);方法调用:对象名.方法名(参数)。
3.3 对象的创建
使用类模板来创建对象,创建对象的格式为: 变量名 = 类名()
;变量名保存的是创建对象的引用。
3.4 使用对象调用方法
格式: 对象名.方法名(参数)
方法调用顺序:
- 1)定义类模板时, Python解释器会进入类模板中定义方法, 但是不会进入方法体中执行代码
- 2)使用类模板创建实例对象 (实例对象就是具体存在的事物)
- 3)当使用对象调用方法时,才会进入方法体中执行代码
- 4)方法调用结束之后会回到调用的地方,继续向下执行
3.5 面向对象中的引用
通过类模板创建对象 对象变量名 = 类名()
, 对象变量名保存的是对象在内存中的引用地址。
变量名可以通过引用地址找到实例对象在内存中具体位置。
通过 类名( )
创建的多个实例对象并不是同一个实例对象。
内存引用地址表示方法:
① 十六进制地址和十进制地址都是表示变量保存数据在内存中引用地址
② 十六进制地址和十进制地址可以相互转换
③ %d 可以以 10 进制 输出数字
④ %x 可以以 16 进制 输出数字
3.6 self参数
- self参数保存当前对象地址的引用 (当前调用方法的对象是哪个,self参数就保存哪个对象的引用)
- 在类外部通过对象调用方法时,不需要手动传入self参数, Python解释器自动传参
- 在类内部访问属性/方法:
self.属性名
/self.方法名()
- 在类外部访问属性/方法:
对象名.属性名
/对象名.方法名()
- 在类内部访问属性/方法:
3.7 给对象添加属性
- 在类的外部添加属性:
对象名.属性名 = 值
(不推荐使用, 如果程序在运⾏时, 没有找到属性, 会报错) - 在类的内部添加属性,在初始化方法内部添加属性。对象应该包含有哪些属性,应该封装在类的内部。
3.8 初始化方法
- 当使用
类名()
创建对象时,会自动执行以下操作:- 为对象在内存中分配空间 —— 创建对象
- 为对象的属性设置初始值 —— 初始化方法
__init__(self,参数列表)
会自动调用
- 初始化方法
__init__
方法是python内置方法 (在某些情况下会自动调用)- 作用: 用来初始化属性数据
- 初始化的同时设置属性值:
格式:
步骤:def __init__(self,name): print("初始化方法 %s" % name) self.name = name
① 把希望设置的属性值,定义成__init__
方法的参数
② 在方法内部使用self.属性 = 形参
接收外部传递的参数
③ 在创建对象时,使用类名(属性1, 属性2...)
调用
3.9 面向对象中的常用内置方法
(1) 内置方法__init__
: 初始化方法
(2) 内置方法__del__
- 当一个对象被从内存中销毁前,会自动调用
__del__
方法。 - 对象的生命周期是从创建对象开始到调用
__del__
方法结束。 - 如果需要手动删除一个对象时,需要使用del 关键字。
(3 )内置方法__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)
#输出:
#Tom 来了
#我是⼩猫: Tom
#Tom 去了
3.10 身份运算符
(1)is 和 is not:身份运算符,⽤于⽐较两个对象的内存地址是否⼀致 —— 是否是对同⼀个对象的引⽤。
(2)在 Python 中针对 None ⽐较时, 建议使⽤ is 判断, 示例: is None
(3) is 与 == 的区别:
- is ⽤于判断 两个变量 引⽤地址是否相同
- == ⽤于判断 引⽤变量的值 是否相等
3.11 私有属性和私有方法
有些情况下,类中一些属性和方法不需要类外实例对象直接访问到,可以把类中的属性和方法定义为私有
格式:
前置双下划线的属性和方法就是私有属性和私有方法, 例如:
(1) __属性名
私有属性
(2) __方法名(self)
私有方法
(3)说明:
- 私有属性和私有方法在类外部不能直接访问,但是可以在类内部访问到
- Python中并没有真正意义上的私有属性和私有方法, 而是通过名字重整的方式将私有属性和私有方法改了名字
- 私有属性和私有方法名字重整后:
- 私有属性名 ->
__类名__属性名
- 私有方法名 ->
__类名__私有方法名
- 私有属性名 ->
- 可以通过重整后的私有属性名和私有方法名,间接去访问, 例如:
对象名._类名__私有属性名
(不推荐间接访问私有属性或私有方法)
4 面向对象3大特性
4.1 封装
封装根据职责将属性和方法封装到一个抽象的类中,外界使用类创建对象, 然后让对象调用方法,对象方法的细节都被封装在类的内部(如3)。
4.2 继承
继承实现代码的重用,相同的代码不需要重复的编写,子类针对自己特有的需求, 编写特定的代码。子类拥有父类的所有方法和属性。
4.2.1 单继承
(1)语法:
class 类名(⽗类名):
pass
- 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发。
- 子类中应该根据职责,封装子类特有的属性和方法。
(2)继承的传递性
C 类从 B 类继承, B 类又从 A 类继承,那么 C 类就具有 B 类和 A 类的所有属性和方法。子类 拥有 父类 以及 父类的父类 中封装的所有 属性 和 方法。
(3)方法的重写
当 父类 的方法实现不能满足子类需求时, 可以对方法进行 重写(override)
- 覆盖父类的方法进行重写:
如果在开发中,父类的方法实现和子类的方法实现完全不同,就可以使用覆盖的方式,在子类中重新编写父类的方法实现,具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现;重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法。 - 扩展父类的方法:
如果在开发中,子类的方法实现中包含父类的方法实现(父类原本封装的方法实现是子类方法的一部分),就可以使用扩展的方式:- 在子类中重写父类的方法
- 在需要的位置使用
super().⽗类⽅法
来调用父类方法的执行(在 Python 中, super 是一个特殊的类,super() 就是使用 super 类创建出来的对象,最常使用的场景就是在重写父类方法时, 调用在父类中封装的方法实现) - 代码其他的位置针对子类的需求, 编写子类特有的代码实现
(3)父类的私有属性和方法:
- 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法。
- 子类对象可以通过父类的公有方法间接访问到私有属性或私有方法
4.2.2 多继承
子类可以拥有多个父类,并且具有所有父类的属性和方法。
语法:
class ⼦类名(⽗类名1, ⽗类名2...)
pass
注意事项:
- 在开发时应该尽量避免不同父类中存在同名方法或属性的情况,若父类间存在同名的属性和方法,应尽量避免使用多继承。
- Python中的方法搜索顺序: 可以通过内置属性
__mro__
可以查看
在搜索方法时,是按照class A: def test(self): pass def demo(self): pass class B: def test(self): pass def demo(self): pass class C(A,B): pass print(C.__mro__) # 输出:(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
__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
4.3 多态
不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度。
多态以继承和重写父类方法为前提,是调用方法的技巧,不会影响到类的内部设计。
5 类属性和类方法
5.1 类的结构
(1)实例
面向对象开发的步骤:
- step1: 设计类
- step2: 使用
类名()
创建对象,创建对象的动作有两步:- (1)在内存中为对象分配空间
- (2) 调用初始化方法
__init__
为对象初始化
- step3: 使用对象调用方法
对象创建后,内存中就有了一个对象的实实在在的存在 ,即实例。
- 通常把:创建出来的对象叫做类的实例;创建对象的动作叫做实例化;对象的属性叫做实例属性;对象调用的方法叫做实例方法
- 在程序执行时:对象各自拥有自己的实例属性;调用对象方法,可以通过
self.
来访问自己的属性或调用自己的方法。每一个对象都有自己独立的内存空间,保存各自不同的属性。多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部。
5.2 类对象
Python 中一切皆对象,class AAA
: 定义的类属于类对象;obj1 = AAA()
属于实例对象。
在程序运行时,类同样会被加载到内存,类是一个特殊的对象(类对象),类对象在内存中只有一份,使用一个类可以创建出很多个对象实例。
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法,即类属性和类方法。
通过类名.
的方式可以访问类的属性或者调用类的方法。
5.3 类属性
类属性就是给类对象定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征。
在Python中,属性的获取存在一个向上查找机制,首先在对象内部查找对象属性,若没有找到,就会向上查找类属性。
访问类属性 可以通过 类名.类属性
或 对象.类属性(不推荐)
,如果使用 对象.类属性 = 值
,只会给对象添加一个属性,而不会影响到类属性的值。
5.4 类方法和静态方法
5.4.1 类方法
类属性就是针对类对象定义的属性,用于记录与这个类相关的特征
类方法就是针对类对象定义的方法,在类方法内部可以直接访问类属性,或者调用其他的类方法。类方法需要用修饰器 @classmethod
来标识,告诉解释器这是一个类方法。
@classmethod def 类⽅法名(cls): pass
- 类方法的第一个参数应该是
cls
,由哪一个类调用的类方法,方法内的cls
就是哪一个类的引用,这个参数和实例方法的第一个参数是self类似。 - 通过
类名.
调用类方法时,不需要传递 cls 参数。 - 在方法内部,可以通过
cls.
访问类的属性或调用其他的类方法。
5.4.2 静态方法
在开发时,如果需要在类中封装一个方法,这个方法既不需要访问实例属性或者调用实例方法,也不需要访问类属性或者调用类方法,就可以把这个方法封装成一个静态方法。
@staticmethod
def 静态方法名()
静态方法需要用修饰器 @staticmethod
来标识,告诉解释器这是一个静态方法,通过 类名.
调用静态方法。
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()
5.4.3 实例方法、类方法和静态方法说明
- 实例方法: 方法内部需要访问实例属性。实例方法内部可以使用
类名.
访问类属性 - 类方法:方法内部只需要访问类属性
- 静态方法: 方法内部,不需要访问实例属性和类属性
5.5 类变量、实例变量、类对象、实例对象举🌰
class Person: # 类对象
x = 9
y = 10
def __init__(self,x,y):
self.x = x
self.y = y
print("x="+str(x))
print("y="+str(y))
def add(self):
return self.x + self.y
p1 = Person(1,2) #实例对象
# x= 1
# y= 2
p2 = Person(3,4) #实例对象
# x= 3
# y= 4
print(p1.x) #实例变量
# 1
print(Person.x) # 类变量
# 9
print(p1.add()) #输出:
# 3
print(Person.add(Person(6,7))) # 传入的对象应该是一个实例对象 #输出:
# x=6
# y=7
# 13
print(Person.add()) #报错:
#TypeError: add() missing 1 required positional argument: 'self'