面向过程和面向对象,是两种不同的编程方式。
特点:
相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法。
特点
类和对象是面向对象编程的两个核心概念。
(1)类
类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用。
特征被称为属性;
行为被称为方法;
类就相当于制造飞机时的图纸,是一个模板,是负责创建对象的
(2)对象
对象是由类创建出来的一个具体存在,可以直接使用。
由哪一个类创建出来的对象,就拥有在哪一个类中定义的:
属性
方法
对象就相当于用图纸制造的飞机。在程序开发中,应该先有类,再有对象。
(3)类和对象的关系
在程序开发中,要设计一个类,通常需要满足一下三个要素:
大驼峰命名法:HelloWorld,每一个单词的首字母大写;单词与单词之间没有下划线。
(1)类名的确定
名词提炼法分析整个业务流程,出现的名词,通常就是找到的类。
(2)属性和方法的确定
提示:需求中没有涉及的属性或者方法在设计类时,不需要考虑。
在Python
中对象几乎是无所不在的,我们之前学习的变量、数据、函数都是对象。
在Python
中可以使用以下两个方法验证:
.
,然后按下TAB
键,ipython
会提示该对象能够调用的方法列表。dir()
传入标识符/数据,可以查看对象内的所有属性及方法。提示:
__方法名__
格式的方法是Python
提供的内置方法/属性。
__new__|方法|创建对象时,会被自动调用
__init__|方法|对象被初始化时,会被自动调用
__del__|方法|对象被从内存中销毁前,会被自动调用
__str__|方法|返回对象的描述信息,print函数输出使用
面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!!
(1)定义只包含方法的类
# 类名的命名格式符合大驼峰命名法
class 类名:
def 方法1(self, 参数列表):
pass
def 方法2(self, 参数列表):
pass
方法的定义格式和之前学习过的函数几乎一样;区别在于第一个参数必须是self
。
(2)创建对象
当一个类定义完成之后,要使用这个类来创建对象:
对象变量 = 类名()
self
的使用在Python
中,给对象设置属性,只需要在类的外部的代码中直接通过.
设置一个属性即可(这种方式虽然简单,但是不推荐使用)。
self
就表示当前调用方法的对象自己;self
就是哪一个对象的引用;self
参数;self.
访问对象的属性,可以通过self.
调用其它对象方法;在类的外部,通过
变量名.
访问对象的属性和方法;在类封装的方法中,通过
self.
访问对象的属性和方法。
__init__
方法在日常开发中,不推荐在类的外部给对象增加属性;如果在运行时,没有找到属性,程序会报错;对象应该包含有哪些属性,应该封装在类的内部。
当使用类名()
创建对象时,会自动执行以下操作:
为对象在内存中分配空间——创建对象
为对象的属性设置初始值——初始化方法(__init__
)
这个初始化方法就是__init__
方法,__init__
是对象的内置方法
__init__
方法是专门用来定义一个类具有哪些属性的方法!在__init__
方法内部使用self.属性名 = 属性的初始值
就可以定义属性。
在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对__init__
方法进行改造:
__init__
方法的参数se1f.属性 = 形参
接收外部传递的参数类名(属性1,属性2..…)
调用class People():
def __init__(self, name):
self.name = name
print("初始化方法%s" % self.name)
xt = People("xiaotang")
xm = People("xiaoming")
# 使用id()函数查看数据在内存中的地址
print(xt, id(xt))
print(xm, id(xm))
# 输出结果
初始化方法xiaotang
初始化方法xiaoming
<__main__.People object at 0x0000027E373FC408> 2741116060680
<__main__.People object at 0x0000027E37E98348> 2741127185224
__del__
和__str__
方法(1)__del__
方法
在Python
中
当使用类名()
创建对象时,为对象分配完空间后,自动调用__init__
方法;
当一个对象被从内存中销毁前,自动调用__del__
方法。
应用场景
__init__
改造初始化方法,可以让创建对象更加灵活;
__del__
如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__
方法。
生命周期
一个对象从调用类名()
创建,生命周期开始;
一个对象的__del__
方法一旦被调用,生命周期结束;
在对象的生命周期内,可以访问对象属性,或者让对象调用方法。
class People():
def __init__(self, name):
self.name = name
print("初始化方法%s" % self.name)
def __del__(self):
print("对象销毁,%s" % self.name)
xt = People("xiaotang")
xm = People("xiaoming")
print(xt, id(xt))
print(xm, id(xm))
# 输出结果
初始化方法xiaotang
初始化方法xiaoming
<__main__.People object at 0x0000027E37EC63C8> 2741127373768
<__main__.People object at 0x0000027E37EC6448> 2741127373896
对象销毁,xiaotang
对象销毁,xiaoming
(2)__str__
方法
Python
中,使用print
输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示);print
输出对象变量
时,能够打印自定义的内容,就可以利用__str__
这个内置方法了。注意:__str__
方法必须返回一个字符串
class People():
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
xt = People("xiaotang")
xm = People("xiaoming")
print(xt, id(xt))
print(xm, id(xm))
# 输出结果(对比上一条代码的输出结果)
初始化方法xiaotang
初始化方法xiaoming
我的名字:xiaotang 2741123224968
我的名字:xiaoming 2741127144648
对象销毁,xiaotang
对象销毁,xiaoming
面向对象三大特性:
封装,根据职责将属性和方法封装到一个抽象的类中。
继承,实现代码的重用,相同的代码不需要重复的编写。
设计类的技巧
子类针对自己特有的需求,编写特定的代码
多态,不同的子类对象调用相同的父类方法,产生不同的执行结果。
多态可以增加代码的灵活度
以继承和重写父类方法为前提
是调用方法的技巧,不会影响到类的内部设计
class Gun:
def __init__(self, model):
# 枪的型号
self.model = model
# 子弹数量
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
def shoot(self):
if self.bullet_count <= 0:
print("没有子弹了")
return
self.bullet_count -= 1
print("%s发射子弹[%d]..."%(self.model, self.bullet_count))
class Soldier:
def __init__(self, name):
# 姓名
self.name = name
# 士兵出事没有枪,None表示什么都没有
self.gun = None
def fire(self):
if self.gun is None:
print("%s没有枪..."% self.name)
return
print("冲啊....[%s]"% self.name)
self.gun.add_bullet(50)
self.gun.shoot()
# 创建对象
ak47 = Gun("ak47")
person = Soldier("xiaotang")
person.fire()
print("*"*20)
person.gun = ak47
person.fire()
# 输出结果
xiaotang没有枪...
********************
冲啊....[xiaotang]
ak47发射子弹[49]...
(1)身份运算符
身份运算符用于比较两个对象的内存地址是否一致,是否是对同一个对象的引用。在Python
中针对None
比较时,建议使用is
判断。
运算符 | 描述 | 实例 |
---|---|---|
is |
is 是判断两个标识符是不是引用同一对象 |
x is y ,类似于id(x) == id(y) |
is not |
is not 是判断两个标识符是不是引用不同对象 |
x is not y ,类似于id(a) != id(y) |
(2)is
与==
区别
is
用于判断两个变量引用对象是否为同一个;
==
用于判断引用变量的值是否相等。
a = [1,2,3]
b = [1,2,3]
print(b is a)
print(b == a)
# 输出结果
False
True
在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问。
私有属性就是对象不希望公开的属性;
私有方法就是对象不希望公开的方法;
在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或者方法。
私有属性/方法,在外界不能够被直接访问;
在对象的方法内部,是可以访问对象的私有属性/方法的。
伪私有属性和私有方法(不建议使用),Python中,并没有真正意义的私有。
在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到;
处理方式:在名称前面加上_类名
,即_类名__名称
。
class People:
def __init__(self, name, age):
self.name = name
self.age = age
self.__height = 180
def __mimi(self):
print("我的身高是%d"% self.__height)
xiaotang = People("小汤", 20)
# 私有属性,外部不能访问
# AttributeError: 'People' object has no attribute '__height'
# print(xiaotang.__height)
# AttributeError: 'People' object has no attribute '__mimi'
# xiaotang.__mimi()
print(xiaotang._People__height) # 180
xiaotang._People__mimi()
# 输出结果
180
我的身高是180
设计模式
设计模式是前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案;
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
单例设计模式
目的–让类创建的对象,在系统中只有唯一的一个实例;
每一次执行类名()
返回的对象,内存地址是相同的。
单例设计模式的应用场景
音乐播放对象
回收站对象
打印机对象
(1)__new__
方法
使用类名()
创建对象时,Python
的解释器首先会调用__new__
方法为对象分配空间;
__new__
是一个由object
基类提供的内置的静态方法,主要作用有两个:
在内存中为对象分配空间
返回对象的引用
Python
的解释器获得对象的引用后,将引用作为第一个参数,传递给__init__
方法。
重写__new__
方法的代码非常固定:
__new__
方法一定要return super().__new__(cls)
Python
的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法__new__
是一个静态方法,在调用时需要主动传递cls
参数class MusicPlayer(object):
def __new__(cls):
# 创建对象时,new方法会被自动调用
print("分配空间")
# 为对象分配空间,返回对象的引用
return super().__new__(cls)
def __init__(self):
print("初始化")
# 创建对象
player = MusicPlayer()
print(player)
# 输出结果
分配空间
初始化
<__main__.MusicPlayer object at 0x000002343B97DD48>
(2)Python
中的单例
单例——让类创建的对象,在系统中只有唯一的一个实例
None
,用于记录单例对象的引用;__new__
方法;is None
,调用父类方法分配空间,并在类属性中记录结果;# 单例设计模式实现
class MusicPlayers():
# 记录第一个被创建对象的引用
instance = None
def __new__(cls):
print("分配空间")
# 判断类属性是否为空对象
if cls.instance is None:
# 调用父类的方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
print("初始化")
# 创建多个实例,无论调用多少次创建对象类的方法,得到的对象内存地址都是相同的
player1 = MusicPlayers()
player2 = MusicPlayers()
print(player1)
print(player2)
# 输出结果
分配空间
初始化
分配空间
初始化
<__main__.MusicPlayers object at 0x000002343B945248>
<__main__.MusicPlayers object at 0x000002343B945248>
(3)存在的问题
问题所在
在每次使用类名()
创建对象时,Python
的解释器都会自动调用两个方法。
__new__
分配空间
__init__
对象初始化
在上一小节对__new__
方法改造之后,每次都会得到第一次被创建对象的引用。
但是:初始化方法还会被再次调用。
需求:让初始化动作只被执行一次。
解决办法
init_flag
标记是否执行过初始化动作,初始值为False
;__init__
方法中,判断init_flag
,如果为False
就执行初始化动作;init_flag
设置为True
;__init__
方法时,初始化动作就不会被再次执行了。class MPlayers():
# 记录第一个被创建对象的引用
instance = None
# 记录是否执行过初始化的动作
init_flag = False
def __new__(cls):
print("分配空间")
# 判断类属性是否为空对象
if cls.instance is None:
# 调用父类的方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
if MPlayers.init_flag:
return
print("初始化了,一次")
MPlayers.init_flag = True
# 创建多个实例,无论调用多少次创建对象类的方法,得到的对象内存地址都是相同的
player3 = MPlayers()
player4 = MPlayers()
print(player3)
print(player4)
# 输出结果
分配空间
初始化了,一次
分配空间
<__main__.MPlayers object at 0x000001E149BB2F08>
<__main__.MPlayers object at 0x000001E149BB2F08>
(1)继承的概念:子类拥有父类的所有方法和属性。
(2)继承的语法
class 类名(父类名):
pass
子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发;
子类中应该根据职责,封装子类特有的属性和方法。
子类 父类
派生类 基类
(3)继承的传递性
(1)方法的重写
子类拥有父类的所有方法和属性。
子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
(2)应用场景
当父类的方法实现不能满足子类需求时,可以对方法进行重写(override
)
重写父类方法有两种情况:①覆盖父类的方法;②对父类方法进行扩展。
①覆盖父类的方法
【具体的实现方式】,就相当于在子类中定义了一个和父类同名的方法并且实现。
重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
②对父类方法进行扩展
如果在开发中,子类的方法实现中包含父类的方法实现,父类原本封装的方法实现是子类方法的一部分。
使用扩展的方式:
super().父类方法
来调用父类方法的执行;(3)super
Python
中super
是一个特殊的类;super()
就是使用super
类创建出来的对象;(4)调用父类方法的另外一种方式(知道)
在Python 2.x
时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
提示:
在开发时,父类名和
super()
两种方式不要混用;如果使用当前子类名调用方法,会形成递归调用,出现死循环。
class People:
def __init__(self, name):
self.name = name
def language(self):
print("地球语言:汉语、俄语、西班牙语、英语、德语...")
class Person(People):
def language(self):
super().language()
print("%s是中国人,说汉语"%self.name)
xiaotang = Person("小汤")
print(xiaotang.name)
xiaotang.language()
# 输出结果
小汤
地球语言:汉语、俄语、西班牙语、英语、德语...
小汤是中国人,说汉语
父类的私有属性和私有方法
私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问;
私有属性、方法通常用于做一些内部的事情。
class People:
def __init__(self, name):
self.name = name
# 私有属性
self.__num = 8000000000
def language(self):
print("地球语言:汉语、俄语、西班牙语、英语、德语...")
# 私有方法
def __protect(self):
print("人数:%d,这是一个秘密"% self.__num)
# 通过公有方法间接调用私有属性和私有方法
def people_num(self):
self.__protect()
class Person(People):
def language(self):
super().language()
print("%s是中国人,说汉语"%self.name)
# 访问父类中私有方法,是访问不到的
# self.__protect()
self.people_num()
xiaotang = Person("小汤")
print(xiaotang.name)
# 访问父类中私有属性,是访问不到的
# print(xiaotang.__num)
xiaotang.language()
# 输出结果
小汤
地球语言:汉语、俄语、西班牙语、英语、德语...
小汤是中国人,说汉语
人数:8000000000,这是一个秘密
子类可以拥有多个父类,并且具有所有父类的属性和方法。
例如:孩子会继承父亲和母亲的特性
# 语法
class 子类名(父类名1,父类名2...):
pass
多继承的使用注意事项
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法。开发时,应该尽量避免这种容易产生混淆的情况!如果父类之间存在同名的属性或方法,应该尽量避免使用多继承。
python
中的MRO
----方法搜索顺序(了解)
python
中针对类提供了一个内置属性__mro__
可以查看方法的搜索顺序。MRO
是method resolution order
,主要用于在多继承时判断方法、属性的调用路径。print(*.__mro__)
# 输出结果
(<class '__main__.*'>,<class '__main__.*'>,<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
多态,不同的子类对象调用相同的父类方法,产生不同的执行结果。
多态可以增加代码的灵活度
以继承和重写父类方法为前提
是调用方法的技巧,不会影响到类的内部设计
class Dog:
def __init__(self, name):
self.name = name
def game(self):
print("%s简单的玩耍"%self.name)
class T_Dog(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()
# 定义一个狗对象
# wangcai = Dog("旺财")
wangcai = T_Dog("飞天旺财")
# 定义一个小明对象
X_Ming = Person("小明")
# 调用一起玩耍的方法
X_Ming.game_with_dog(wangcai)
# 输出结果
小明和飞天旺财一起快乐的玩耍
飞天旺财在天上玩耍
(1)实例
使用类名()
创建对象(分配空间、对象初始化)后,内存中就有了一个对象的实实在在的存在–实例。
在程序执行时:
对象各自拥有自己的实例属性;
调用对象方法,可以通过self.
访问自己的属性
调用自己的方法
结论
(2)类是一个特殊的对象
Python
中一切皆对象:
class AAA
:定义的类属于类对象obj1=AAA()
属于实例对象
在程序运行时,类同样会被加载到内存;
在Python
中,类是一个特殊的对象–类对象;
在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例;
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法:
类属性
类方法
通过类名.
的方式可以访问类的属性或者调用类的方法。
类名.类属性
类名.方法名()
(1)概念和使用
(2)属性的获取机制
在Python
中属性的获取存在一个向上查找机制。因此,要访问类属性有两种方式:
类名.类属性
对象.类属性
(不推荐),遵循向上查找机制(先在对象内部查找,没有就会向上查找类属性)注意:如果使用【对象.类属性=值赋值语句】,只会给对象添加一个属性,而不会影响到类属性的值。
class Tool(object):
# 使用赋值语句,定义类属性,记录创建工具对象的总数
count = 0
def __init__(self, name):
self.name = name
# 计数
Tool.count += 1
# 创建工具对象
tool1 = Tool("钳子")
tool2 = Tool("斧头")
tool3 = Tool("锤子")
# 使用Tool类创建了多少个对象
print("现在创建了%d个工具"% Tool.count)
# 输出结果
现在创建了3个工具
(1)类方法
类属性就是针对类对象定义的属性
class
关键字下方可以定义类属性;类方法就是针对类对象定义的方法
# 语法
@classmethod
def 类方法名(cls):
pass
类方法需要用修饰器@classmethod
来标识,告诉解释器这是一个类方法。
类方法的第一个参数应该是cls
由哪一个类调用的方法,方法内的cls
就是哪一个类的引用;
这个参数和实例方法的第一个参数是self
类似;
提示:使用其他名称也可以,不过习惯使用cls
。
通过类名.
调用类方法,调用方法时,不需要传递cls
参数。
在方法内部
可以通过c1s.
访问类的属性
也可以通过c1s.
调用其他的类方法
class Tool(object):
# 使用赋值语句,定义类属性,记录创建工具对象的总数
count = 0
def __init__(self, name):
self.name = name
# 计数
Tool.count += 1
@classmethod
def show_tool_count(cls):
print("工具对象的总数:%d" % cls.count)
# 创建工具对象
tool1 = Tool("钳子")
tool2 = Tool("斧头")
tool3 = Tool("锤子")
# 使用Tool类创建了多少个对象
print("现在创建了%d个工具"% Tool.count)
Tool.show_tool_count()
# 输出结果
现在创建了3个工具
工具对象的总数:3
在类方法内部,可以直接使用
cls
访问类属性或者调用类方法。
(2)静态方法
在开发时,如果需要在类中封装一个方法,这个方法:
既不需要访问实例属性或者调用实例方法
也不需要访问类属性或者调用类方法
这个时候,可以把这个方法封装成一个静态方法语法如下。
# 语法
@staticmethod
def 静态方法名():
pass
静态方法需要用修饰器@staticmethod
来标识,告诉解释器这是一个静态方法。
通过类名.
调用静态方法,不需要创建对象。
class Game:
# 类属性
top_score = 100
# 实例属性,在初始化方法内部定义
def __init__(self,player_name) -> None:
self.name = player_name
# 静态方法
@staticmethod
def show_help():
print("帮助信息")
# 类方法
@classmethod
def show_top_score(cls):
# print("历史最高分:%d"%Game.top_score)
print("历史最高分:%d"%cls.top_score)
# 实例方法
def start_game(self):
print("%s开始的游戏"%self.name)
# 【类名.】
Game.show_help()
Game.show_top_score()
# 创建对象,【对象.】
Tom = Game("小汤")
Tom.start_game()
# 输出结果
帮助信息
历史最高分:100
小汤开始的游戏
小结
- 实例方法–方法内部需要访问实例属性
- 实例方法–方法内部可以使用
类名.
访问类属性- 类方法–方法内部只需要访问类属性,或类方法
- 静态方法–方法内部,不需要访问实例属性和类属性
如果方法内部即需要访问实例属性,又需要访问类属性,应该定义成什么方法?(实例方法)
笔者不才,请多交流!!!
参考文献:黑马程序员《Python
入门教程完整版》
欢迎大家关注预览我的博客Blog:HeartLoveLife
能力有限,敬请谅解!!