python入门(9)对象
一、面向过程和面向对象基本概念
1. 面向过程完成某一个需求的所有步骤从头到尾逐步实现
根据开发需求,将某些功能独立的代码封装成一个又一个函数
最后完成的代码,就是顺序地调用不同的函数
- 特点
注重步骤与过程,不注重职责分工
如果需求复杂,代码会变得很复杂
开发复杂项目,没有固定的套路,开发难度很大!
2.面向对象–谁来做
相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法
- 在完成某一个需求前,首先确定职责—要做的事情(方法)
- 根据职责确定不同的对象,在对象内部封装不同的方法(多个)
- 最后完成的代码,就是顺序地让不同的对象调用不同的方法
- 特点
注重对象和职责,不同的对象承担不同的职责
更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
需要在面向过程基础上,再学习一些面向对象的语法。
二、类和对象的概念
1.类
- 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
特征被称为属性
行为被称为方法 - 类就相当于制造飞机时的图纸,是一个模板,是负责创建对象的
2.对象
- 对象是由类创建出来的一个具体存在,可以直接使用
- 由哪一个类创建出来的对象,就拥有哪一个类中定义的属性和方法
- 对象就相当于图纸制造的飞机
- 在程序开发中,应该先有类,再有对象
3.类和对象的关系
- 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象
- 类只有一个,而对象可以有很多个,不同的对象之间属性可能会各不相同
- 类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少
4.类的设计
- 在使用面向对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类!
- 在程序开发时,要设计一个类,通常需要满足以下三个要素:
类名:这类事物的名字,满足大驼峰命名法
属性:这类事物具有什么样的特征
方法:这类事物具有什么样的行为 - 大驼峰命名法
CapWords
每一个单词的首字母大写
单词与单词之间没有下划线 - 练习
三、面向对象基础语法
1.dir内置函数
- 变量、数据、函数都是对象
- 使用内置函数dir传入标识符/数据,可以查看对象内的所有属性及方法
- 提示 方法名 格式的方法是python提供的内置方法/属性。
2.定义简单的类(只包含方法)
- 面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!
- 定义只包含方法的类
语法格式
class 类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
方法的定义格式和之前学习过的函数几乎一样,区别在于第一个参数必须是self。 - 创建对象
对象变量 = 类名( )
- 案例
需求:小猫爱吃鱼,小猫要喝水
分析:定义一个猫类;定义两个方法eat和drink;
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
# 创建猫对象
tom = Cat()
tom.eat()
tom.drink()
- 引用概念
在面向对象开发中,引用的概念是同样适用的
在python中使用类创建对象之后,tom变量中仍然记录的是对象在内存中的地址,也就是tom变量引用了新建的猫对象。
使用print输出对象变量,默认情况下,是能够输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
提示:在计算机中,通常使用十六进制表示内存地址
%d 可以以10进制输出数字
%x 可以以16进制输出数字
3.方法中的self参数
- 给对象增加属性(不推荐)
在类的外部的代码中直接通过.设置一个属性即可
如:tom.name = "Tom"
- 使用self在方法内部访问对象的属性
由哪一个对象调用的方法,方法内的self就是哪一个对象的引用。
在调用方法时,程序员不需要传递self参数
在方法内部:可以通过self.
访问对象的属性,也可以通过self.
调用其他的对象方法
案例:
class Cat:
def eat(self):
print("%s 爱吃鱼" % self.name)
def drink(self):
print("%s 要喝水" % self.name)
# 创建猫对象
tom = Cat()
tom.name = "Tom"
tom.eat()
tom.drink()
4.初始化方法
- 之前代码存在的问题–在类的外部给对象增加属性
- 在日常开发中,不推荐在类的外部给对象增加属性,应该封装在类的内部
- 当使用类名( )创建对象时,会自动执行以下操作:
为对象在内存中分配空间 — 创建对象
为对象的属性 设置初始值 — 初始化方法(init ), - 初始化方法 是
__init__
方法,__init__
方法是对象的内置方法,专门用来定义一个类具有哪些属性的方法
class Cat:
def __init__(self):
print("这是一个初始化方法")
# 创建猫对象
tom = Cat()
- 在初始化方法内部定义属性
在 **__init__
**方法内部使用self.属性名 = 属性的初始值
就可以定义属性,定义属性之后,再使用Cat类创建的对象,都会拥有该属性
class Cat:
def __init__(self):
print("这是一个初始化方法")
# self.属性名 = 属性的初始值
self.name = "Tom"
def eat(self):
print("%s 爱吃鱼" % self.name)
# 创建猫对象
tom = Cat()
print(tom.name)
tom.eat()
- 改造初始化方法 – 初始化的同时设置初始值
在开发时,如果希望在创建对象的同时,就设置对象的属性,可以对__init__
方法进行改造;把希望设置的属性值,定义成__init__
方法的参数,在方法内部使用self.属性 = 形参
接收外部传递的参数;在创建对象时,使用类名(属性1,属性2...)
调用
class Cat:
def __init__(self,name):
print("这是一个初始化方法")
# self.属性名 = 属性的初始值
self.name = name
# 创建猫对象
tom = Cat("Tom")
lazy_cat = Cat("LazyCat")
print(tom.name)
print(lazy_cat.name)
5.内置方法和属性
方法名 | 类型 | 作用 |
---|---|---|
__del__ | 方法 | 对象被从内存中销毁前,会被自动调用 |
__str__ | 方法 | 返回对象的描述信息,print 函数输出使用 |
- del方法使用
class Cat:
def __init__(self,name):
self.name = name
print("%s 来了" % self.name)
def __del__(self):
print("%s 去了" % self.name)
# 创建猫对象
tom = Cat("Tom")
print(tom.name)
# del 关键字可以删除一个对象
del tom
print("*" * 50)
- 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 = Cat("Tom")
print(tom)
6.私有属性和私有方法
- 私有属性:对象不希望公开的属性;
- 私有方法:对象不希望公开的方法,
- 如果对象的某些属性和方法只希望在对象的内部被使用,而不希望在外部被访问到,就可以使用私有属性和私有方法。
- 定义方式
在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法
class Person:
def __init__(self,name,age):
self.name = name
self.__age = age
def secret(self):
# 在对象的方法内部,是可以访问对象的私有属性的
print("%s 的年龄是%d " % (self.name,self.__age))
# 私有属性在外界不能够被直接访问
# 私有方法同样不允许在外界直接访问
tom = Person("Tom",14)
tom.secret()
- 伪私有属性和私有方法
在日常开发中,不要使用这种方式,访问对象的私有属性或私有方法,python中并没有真正意义的私有。
在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到,处理方法:在名称前面加上_类名
,即_类名__名称
# 私有属性在外界不能够被直接访问
# Woman;类名 age:私有属性
print(xiaofang._Woman__age)
# 私有方法同样不允许在外界直接访问
# secret:私有方法
print(xiaofang._Woman__secret())
四、继承
封装:根据职责将属性和方法封装到一个抽象的类中
继承:实现代码的重用,相同的代码不需要重复的编写
多态:不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
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 bark(self):
print("汪汪叫")
#创建一个对象 - 狗对象
wangcai = Dog()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
- 继承的传递性
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 XiaoTianQuan(Dog):
def fly(self):
print("我可以飞")
#创建一个对象 - 狗对象
wangcai = XiaoTianQuan()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()
wangcai.fly()
2.方法的重写
- 覆盖父类方法,重写子类方法
重写父类方法有两种情况:覆盖父类的方法;对父类方法进行扩展 - 覆盖父类的方法
如果在开发中,父类的方法实现和子类的方法实现完全不同,就可以使用覆盖的方式,在子类中重新编写父类的方法实现。
具体的实现方式:相当于在子类中定义了一个和父类同名的方法并且实现,重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法。
class Dog(Animal):
def bark(self):
print("汪汪叫")
class XiaoTianQuan(Dog):
def fly(self):
print("我可以飞")
def bark(self):
print("叫的跟神一样")
#创建一个对象 - 狗对象
wangcai = XiaoTianQuan()
# 如果子类中,重写了父类的方法
# 在使用子类对象调用方法时,会调用子类中重写的方法
wangcai.bark()
- 对类方法进行扩展
父类原本封装的方法实现是子类方法的一部分就可以使用扩展的方式重写父类
扩展的方法:在子类中重写父类的方法;在需要的的位置使用super.父类方法
来调用父类方法的执行;代码其他的位置针对子类的需求,编写子类特有的代码实现
关于super:在python中super是一个特殊的类;super()就是使用super类创建出来的对象;最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现。
class Dog:
def bark(self):
print("汪汪叫")
class XiaoTianQuan(Dog):
def fly(self):
print("我可以飞")
def bark(self):
# 1.针对子类特有的需求
print("叫的跟神一样")
# 2.使用super( ).调用原本在父类中封装的方法
super().bark()
# 3.增加其他子类的代码
print("sdkjflksjfe")
wangcai = XiaoTianQuan()
wangcai.bark()
3.父类的私有属性和私有方法
- 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
- 子类对象可以通过父类的共有方法间接访问到私有属性或私有方法
私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问
私有属性、方法通常用于做一些内部的事情
4.多继承
- 子类可以拥有多个父类,并且具有所有父类的属性和方法
- 语法
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()
- MRO(method resolution order) – 方法搜索顺序
python中针对类提供了一个内置属性__mro__
可以查看方法搜索顺序,主要用于在多继承时判断方法、属性的调用路径
# C为类名
print(C.__mro__)
在搜索方法时,是按照 __mro__
的输出结果从左到右的顺序查找的;如果在当前类中找到方法,就直接执行,不再搜索;如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索;如果找到最后一个类,还没有找到方法,程序报错。
- 新式类与旧式(经典)类
object是python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir函数查看
新式类:以object为基类的类,推荐使用
经典类:不以object为基类的类,不推荐使用
在python3.x中定义类时,如果没有指定父类,会默认使用object作为该类的基类—在python3.x中定义的类都是新式类
在python2.x中定义类时,如果没有指定父类,则不会以object作为基类
新式类和经典类在多继承时—会影响到方法的搜索顺序
五、多态
- 多态:不同的子类对象调用相同的父类方法,产生不同的执行结果
- 多态可以增加代码的灵活度;以继承和重写父类方法为前提;是调用方法的技巧,不会影响到类的内部设计。
- 案例
class Dog:
def __init__(self,name):
self.name = name
def game(self):
print("%s 蹦蹦跳跳的玩耍..." % self.name)
class XiaoTianQuan(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()
#1.创建一个对象 - 狗对象
# wangcai = Dog("旺财")
wangcai = XiaoTianQuan("飞天旺财")
#2.创建一个小明对象
xiaoming = Person("小明")
#3.让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)
案例小结:
person类中只需要让狗对象调用game方法,而不关心具体是什么狗,game方法是在Dog父类中定义的
在程序执行时,传入不同的狗对象实参,就会产生不同的执行效果
六、类属性
- 每一个对象都有自己独立的内存空间,保存各自不同的属性
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部
1、类是一个特殊的对象
python中一切皆对象
class AAA:
定义的类属于类对象
obj1 = AAA()
属于实例对象
在程序运行时,类同样会被加载到内存
在python中,类是一个特殊的对象 — 类对象
在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例
除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法:类属性
和类方法
通过类名
.的方式可以访问类的属性
或者调用类的方法
2.类属性和实例属性
- 类属性:就是给类对象中定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体的特征
- 示例需求:定义一个类,每件工具都有自己的name,需求—知道使用这个类,创建了多少个工具
class Tool(object):
# 使用赋值语句定义类属性,记录所有工具对象的数量
count = 0
def __init__(self,name):
self.name = name
# 让类属性的值+1
Tool.count += 1
# 1.创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("水桶")
# 2.输出工具对象的总数
print(Tool.count)
- 属性获取机制
在python中属性的获取存在一个向上查找机制
访问类属性有两种方式:类名.类属性 ; 对象.类属性 (不推荐)
注意:如果使用对象.类属性 = 值
赋值语句,只会给对象添加一个属性,而不会影响到类属性的值
3.类方法
- 类方法就是针对类对象定义的方法:在类方法内部可以直接访问类属性或者调用其他的类方法
- 语法如下:
@classmethod
def 类方法名(cls):
pass
- 类方法需要用修饰器
@classmethod
来标识,告诉解释器这是一个类方法 - 类方法的第一个参数应该是cls;由哪一个类调用的方法,方法内的cls就是哪一个类的引用,这个参数和实例方法的第一个参数是self类似,提示使用其他名称也可以,不过习惯cls
- 通过
类名.
调用类方法,调用类方法时,不需要传递cls参数 - 在方法内部,可以通过
cls.
访问类的属性,也可以通过cls.
调用其他的类方法
class Tool(object):
# 使用赋值语句定义类属性,记录所有工具对象的数量
count = 0
@classmethod
def show_tool_count(cls):
print("工具对象的数量 %d" % cls.count)
def __init__(self,name):
self.name = name
# 让类属性的值+1
Tool.count += 1
# 1.创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("水桶")
# 2.调用类方法
Tool.show_tool_count()
4.静态方法
- 静态方法:类中封装的一个方法,这个方法既不需要访问实例属性或者调用实例方法,也不需要访问类属性或者调用类方法。
- 语法如下:
@staticmethod
def 静态方法名():
pass
- 静态方法需要用修饰器
@staticmethod
来标识,告诉解释器这是一个静态方法 - 通过
类名.
调用静态方法
class Dog:
@staticmethod
def run():
#不访问实例属性/类属性
print("小狗要跑....")
# 通过类名.调用静态方法 - 不需要创建对象
Dog.run()
七、单例
1.单例设计模式
- 设计模式:是总结前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
- 单例设计模式
目的— 让类创建的对象,在系统中只有唯一的一个实例
每一次执行类名()
返回的对象,内存地址是相同的 - 单例设计模式的应用场景
音乐播放对象
回收站对象
打印机对象
2.__new__
方法
- 使用
类名()
创建对象时,python解释器首先会调用__new__
方法为对象分配空间 __new__
是一个由object基类提供的内置的静态方法,主要作用有两个:在内存中为对象分配空间,返回对象的引用- python的解释器获得对象的引用后,将引用作为第一个参数,传递给
__init__
方法 - 重写
__new__
方法的代码非常固定, 重写__new__
方法一定要return super().__new__(cls)
,否则python的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法 - 注意:
__new__
是一个静态方法,在调用时需要主动传递cls参数
- new方法重写代码
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
# 1.创建对象时,new方法会被自动调用
print("创建对象,分配空间")
# 2.为对象分配空间
instance = super().__new__(cls)
# 3.返回对象的引用
return instance
def __init__(self):
print("播放器初始化")
# 创建播放器对象
player = MusicPlayer()
3.python中的单例
- 单例— 让类创建的对象,在系统中只有唯一的一个实例
定义一个类属性,初始值是None,用于记录单例对象的引用
重写__new__
方法
如果类属性is None ,调用父类方法分配空间,并在类属性中记录结果
返回类属性中记录的对象引用
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
def __new__(cls, *args, **kwargs):
# 1.判断类属性instance是否是空对象
if cls.instance is None:
# 2.调用父类的方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
# 3.返回类属性保存的对象引用
return cls.instance
def __init__(self):
print("播放器初始化")
# 创建播放器对象
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
- python单例扩展:—只执行一次初始化工作
在每次使用类名()创建对象时,python的解释器都会自动调用两个方法:__new__
分配空间;__init__
对象初始化
需求:让初始化动作只被执行一次
解决办法:定义一个类属性init_flag
标记是否执行过初始化动作,初始值为False;在__init__
方法中,判断init_flag
,如果为False
就执行初始化动作;然后将init_flag
设置为True
;这样,再次自动调用__init__
方法时,初始化动作就不会被再次执行了
让初始化动作只被执行一次代码:
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
# 记录是否执行过初始化动作
init_flag = False
def __new__(cls, *args, **kwargs):
# 1.判断类属性instance是否是空对象
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)