面向对象
两个核心概念 类 和 对象
类是一个模板
对象则是按照这个模板创建出来的实例
类和对象的关系
类是模板,对象是根据类这个模板创建出来的
应该先有类 后有对象
类只有一个,而对象可以有多个
不同对象之间的属性可能会不同
类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多也不可能少
要设计一个类通常要满足
- 类名:大驼峰命名法 首字母大写
- 属性:这类事物具有什么样的特征
- 方法:这类事物具有什么样的行为
属性和方法的确定
- 对象的特征描述:通常定义为属性
- 对象具有的行为(动词):通常定义为方法
面向对象三大特性
- 封装:封装是根据职责将属性和方法封装到一个抽象的类中
- 继承:继承实现代码的重用,相同代码不需要反复编写
- 多态:不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
面向对象基本语法
在python中,一切皆为对象 变量、函数、数据都是对象
封装
概念
广义的封装:函数的定义和类的提取,都是封装的体现。
狭义的封装:在面向对象编程中,一个类的某些属性,在使用的过程中,如果不希望被外界直接访问,就可以将该属性封装(将不希望被外界直接访问的属性私有化private,该属性只能被当前类持有,此时可以给外界暴露一个访问的函数即可)。
封装的本质:就是属性私有化的过程。
封装的好处:提高了数据的安全性,提高了数据的复用性。
dir内置函数
使用dir(对象名) 可以查看对象内所有的属性和方法
带有__ __的方法名 是python针对对象提供的方法或者属性
在python中创建对象之后 对象变量中仍然记录的是对象在内存中的地址
使用print输出对象变量,默认情况下,能够输出这个变量引用的对象是由哪一个类创建的对象
以及在内存中的地址(以16进制表示)
在计算机中 通常使用十六进制表示内存地址
%d 以10进制输出数字
%x 以 16进制输出数字
可以直接在类的外部给对象添加属性 这种方法不推荐使用 有隐患 应该在内部封装
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫爱喝水")
# 创建猫对象
tom = Cat()
tom.name = "Tom"
# 此处直接在类的外部给tom添加name属性
self属性
在类中定义方法时候必须有self属性
由哪一个对象调用的方法,方法内部的self就是哪一个对象的引用(self就是实例对象的引用)
在类封装的方法内部,self就表示当前调用方法的对象自己
初始化方法 init
当使用 类名() 创建对象时,会自动执行以下操作
1.为对象在内存中分配空间–创建对象
2.为对象的属性 设置初始值----初始化方法 #init
init是对象的内置方法
Init方法是专门用来定义一个类具有哪些属性的方法
在创建对象的时候会被自动调用
在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对init方法改造
- 把希望设置的属性值,定义称为init方法的参数
- 在方法内部使用 self.属性= 形参 接收外部传递参数
- .在创建对象时,使用类名(属性1,属性2)调用
# 实例
class Cat():
def __init__(self, new_name):
# 此处设置形参 new_name 方便接收外部传递参数
print("这是初始化方法")
# self.name = "Tom"
self.name = new_name
def eat(self):
print("%s 爱吃鱼" % self.name)
tom = Cat("汤姆") # 创建对象时,传入参数
print(tom.name)
tom.eat()
lazy_cat = Cat("大懒猫")
lazy_cat.eat()
两个内置方法
- del
- str
①与初始化方法对立的内置方法del方法
在python中,当使用类名()创建对象时候,会为对象分配玩空间后自动调用init方法
当一个对象被从内存中销毁前,会自动调用del方法
如果希望在对象被销毁之前再做一些事情,可以使用del方法
生命周期
一个对象从调用类名()创建,生命周期开始
一个对象的del方法一旦被调用,生命周期结束
在对象的声明周期内,可以访问对象属性,或者让对象调用方法
②str方法
在python中,使用print输出对象变量,
默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象以及在内存中的地址(16进制)
如果在开发中,希望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): # 希望print(tom)返回自定义内容时使用
# 必须返回一个字符串
return "我是小猫[%s]" % self.name
tom = Cat("Tom")
print(tom)
私有属性以及私有方法
在实际开发中,对象的某些属性或者方法 可能只希望在对象内部使用,而不希望外部访问到
此时就可以定义私有属性和私有方法
在定义的属性前加上 双下划线__即可
其实在python中,并没有真正意义上的私有 只是做了特殊处理
实际那个是对名称做了一些特殊的处理,从而使外界访问不到
处理的方式为:在名称前面加上 _类名 也就是 _类名__名称
就是在属性或者方法前加上 一个下划线加上创建他们的类名
子类对象不能够直接访问父类的私有属性或者私有方法
但是子类对象可以通过父类的公有方法间接访问父类的私有属性以及私有方法
继承
继承:子类拥有父类的所有方法和属性
class 类名(父类名):
pass
继承具有传递性:子类拥有父类以及父类的父类所具有的属性和方法
当父类的方法实现不能满足子类需求时,可以对方法重写
方法的重写
1、覆盖父类方法
在子类中重新定义方法即可
2、对父类方法进行扩展
①在子类中重写父类方法
②在合适的位置使用super().父类方法
判断一个实例是否是某个类创建出来的 isinstance(object, Cat) 函数来判断object是否为Cat的父类
判断是否为其子类 issubclass(Dog, Animal) >> True
调用父类方法 super(Cat, self).eat()
多继承
在python中,一个子类可以拥有多个父类,并且拥有多个父类的属性和方法就叫做多继承
class C (A, B):
pass
多继承使用注意事项
如果不同父类中存在同命方法或者属性,应该尽量避免使用多继承
python中的MRO——方法搜索顺序
python中针对类提供了一个内置属性__mro__,可以查看方法搜索顺序
MRO 是 method resolution order,主要用于多继承时判断方法、属性的调用路径
print(C.__mro__)
新式类与旧式(经典)类
新式类:以object为基类的类
经典类:不以object为基类的类
多态
多态:不同的子类对象调用相同的父类方法,产生不同的执行结果
多态可以层架代码的灵活度
以继承和重写父类方法为前提
是调用方法的技巧,不会影响到类的内部设计
术语——实例
在面向对象开发时,第一步是设计类
使用类名()创建对象,创建对象的动作有两步
1、在内存中为对象分配空间
2、调用初始化方法init为对象初始化
对象创建后,内存中就有了一个对象的实实在在的存在——实例
通常把
1、创建出来的对象叫做类的实例
2、创建对象的动作叫做实例化
3、对象的属性叫做实例属性
4、对象调用的方法叫做实例方法
实例方法
类是一个特殊的对象
在python中,一切皆对象
class AAA:定义的类属于类对象
aaa = AAA():通过类创建的对象属于实例对象
在程序运行时,类同样会被加载到内存,类对象在内存中只有一份。
类对象还可以拥有自己的属性和方法叫做类属性和类方法
类方法
类属性:就是针对类对象定义的属性,类属性用于记录这个类相关的特征
类方法:就是针对类对象定义的方法
类方法定义如下
@classmethod
def 类方法名称(cls):
pass
类方法需要用修饰器@classmethod来标识,告诉解释器这是一个类方法
类方法的第一个参数应该是cls 这个参数和实例方法的self参数类似
静态方法
在开发时,如果需要在类中封装一个方法、
这个方法既不需要访问实例属性或者调用实例方法
也不需要访问类属性或者调用类方法
这个时候可以把这个方法封装成一个静态方法
语法如下
@staticmethod
def 静态方法名():
pass
静态方法不访问类属性、实例属性、类方法、实例方法
静态方法不需要创对象,直接使用类名.静态方法调用
class Dog(object):
@staticmethod
def run(): # 不需要传递参数
# 不访问实例属性或者类属性
print("小狗快跑...")
# 通过类名.的方式调用静态方法 -- 不需要创建对象
Dog.run()
实例属性定义在init方法中
类属性直接定义在类中
三种方法综合实例
class Game(object):
# 类属性 历史最高分
top_score = 0
# 实例属性定义在init方法中
def __init__(self, player_name):
self.player_name = player_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)
# 1. 查看游戏帮助信息
Game.show_help()
# 2. 查看历史最高分
Game.show_top_score()
# 3. 创建游戏对象
game = Game("小明")
game.start_game()
1.当方法内部需要访问实例属性的时候定义为------实例方法
实例方法内部可以使用类名.访问类属性
2.当方法内部只需要访问类属性时候定义为-----类方法 @classmethod
3.当方法内部既不需要访问实力属性,也不需要访问类属性时候定义为-----静态方法 @staticmethod
如果方法内部即需要访问实例属性,又需要访问类属性
应该定义为实例方法,因为类只有一个,在实例方法中可以通过类名.访问类属性
单例设计模式
设计模式
1、设计模式是前人工作的总结和提炼,通常被人们广泛流传的设计模式都是针对某一特定问题的成熟解决方案
2、使用设计模式是为了可重用代码、让代码更容易被他人理解,保证代码可靠性
单例设计模式
1、目的——让类创建的对象,在系统中只有唯一的一个实例
2、每一次执行类名()创建的对象,内存地址是相同点
单例设计模式应用场景
1、音乐播放器对象
2、回收站对象
3、打印机对象
在系统中是唯一存在对象
内置方法new方法
在python中,使用类名()创建对象时,python解释器会首先调用__new__方法为对象分配空间
__new__是一个由object基类提供的内置静态方法,主要作用有两个
1、在内存中为对象分配空间
2、返回对象的引用
python解释器在获得对象的引用之后,将引用作为第一个参数,传递给init方法
在python中,使用类名()创建对象时,调用过__new__方法为对象分配空间之后才会执行初始化操作
想要实现单例模式,需要对new方法进行重写,重写方法非常固定
重写new方法一定要 return super(),__new__(cls),否则python解释器得不到分配了空间的对象引用,就不会执行初始化方法
注意:new方法是一个静态方法,在调用时需要主动传入cls参数
__new__ 方法简单使用
# __new__ 方法简单使用
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
# 1. 创建对象时,new方法会被自动调用
print("创建对象,分配空间")
# 2. 为对象分配空间
instance = super().__new__(cls)
# 3. 返回对象的引用 如果没有return 将不会执行初始化方法
return instance
def __init__(self):
print("播放器初始化")
player = MusicPlayer()
print(player)
__new__ 方法的重写实现单例模式
# __new__ 方法的重写实现单例模式
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
init_flag = False # 这里为了让初始化方法只执行一次设置了一个参数标志
def __new__(cls, *args, **kwargs):
# 1. 判断类属性是否为空对象
if cls.instance is None:
# 2. 调用父类方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
# 3. 返回类属性保存的对象引用
return cls.instance
def __init__(self):
# 判断是否执行过初始化方法
if MusicPlayer.init_flag:
return
# 如果没有执行过,执行初始化方法
print("初始化播放器")
# 修改类属性标记
MusicPlayer.init_flag = True
# 创建多个对象
player1 = MusicPlayer() # 没重写new方法之前每个对象内存地址不同
print(player1)
player2 = MusicPlayer() # 重写new方法之后每个对象内存地址相同 即为同一对象
print(player2)
异常
程序在运行时,如果python解释器遇到了一个错误,则会停止程序的执行,并且提示一些错误信息,这就是异常 (这个错误信息是提示给程序员看到的,而用户看不到,只能看到停止执行操作)
程序停止执行并且提示错误信息的这个动作,叫做抛出异常
捕获异常完整代码
# 捕获异常完整代码
try:
num = int(input("请输入一个整数:"))
result = 8/num
print(result)
except ValueError:
print("请输入正确的整数")
except Exception as e:
print("未知错误 %s " % e)
# else 是当没有出现异常的时候执行的
else:
print("未出现异常")
# finally是 无论是否出现异常们,下面代码都会执行
finally:
print("无论是否出现异常,都会执行的代码")
主动抛出异常
在开发中,除了代码执行错误之后python解释器抛出异常之外
还可以根据应用程序特有的业务需求 主动抛出异常
主动抛出异常语法
1.创建一个exception对象
2.使用raise关键字抛出异常对象
# 主动抛出异常
def input_password():
# 1. 提示用户输入密码
pwd = input("请输入密码:")
# 2. 判断密码长度>=8,返回用户输入的密码
if len(pwd) >= 8:
return pwd
# 3. 如果 <8 则主动抛出异常
print("主动抛出异常")
# 1>创建异常对象
ex = Exception("密码长度不够")
# 2>主动抛出异常
raise ex
# 主动抛出异常后还可以捕获抛出的异常
try:
print(input_password())
except Exception as e:
print("未知错误%s" % e)
模块导入
每一个独立的py文件就是就是一个模块,就可以被导入
1、import
2、from … import …
如果希望从某一个模块中,导入部分工具,就可以使用from import 的方式,直接访问工具名
import 模块名是一次性把模块中的所有工具全部导入,并通过模块名.工具名的方式访问
模块的搜索路径
python解释器在导入模块时候,
1.会搜索当前目录下指定模块名的文件,如果有就直接导入
2.如果没有,再搜素系统目录
原则——每一个文件都应该是可以被导入的
一个独立的py文件就是一个模块
在导入文件时,文件中所有没有任何缩进的代码都会被执行一遍
在实际开发场景中,每一个模块都是独立开发的,大多有专人负责
开发人员通常会在模块下方增加一些测试代码,这些代码仅仅用来在当前模块下执行,而被导入其他文件中不需要执行
__name__属性
__name__属性可以做到测试模块的代码 只在测试情况下被运行,而在被导入的情况下不会被执行
name 是python中的一个内置属性,记录这 一个字符串
如果是被其他文件导入的,那么他记录的就是 模块名
如果是当前执行的程序,那么他记录的就是main
# 导入模块
# 定义全局变量
# 定义类
# 定义函数
# 在代码最下方
def main():
pass
# 根据__name__ 判断是否执行以下代码
if __name__ == "__main__":
main()
包(Paceage)
包是一个包含多个模块的特殊目录
目录下有一个特殊的文件 init.py
包的好处:import 包名 可以一次性导入包中所有的模块
如果开发中,需要使用多个不同的模块 可以将多个文件打包,package
要在外界使用包中的模块,需要在init.py中指定对外界提供的模块列表
# 在init文件中指定对外界提供的模块列表
from . import send_message
from . import receive_message
文件读写
文件的作用
将数据长期保存下来,在需要的时候使用
文件的存储方式
在计算机中,文件是以二进制的形式保存在磁盘上的
操作文件的套路
1、打开文件
2、读、写文件
3、关闭文件 如果忘记关闭文件,会造成系统资源的浪费
很多时候会忘记进行文件关闭 f.close()
因此使用with语法可以自动关闭文件,避免忘记关闭操作
with open('readme.txt') as f:
f.write(text)
操作文件的函数
一个函数(open) 三个方法(read/write/close)
open 打开文件,并且返回文件操作对象
read 将文件内容读取到内存
write 将制定内容写入文件
close 关闭文件
open函数负责打开文件,并且返回文件对象,read/write/close 三个方法都需要通过文件对象来调用
文件指针
文件指针标记从哪个位置开始读取数据
当第一次打开文件时,通常文件指针会指向文件的开始位置
当执行了read方法后,文件指针会移动到内容尾部
因此 如果执行了一次read方法,读取了所有的内容,那么再次调用read方法,将不能读取内容
打开文件的方式
open函数默认以只读的方式打开文件
语法如下
f = open("文件名", "访问方式")
r 以只读的方式打开文件,这是默认模式。 如果文件不存在会抛出异常
w 以只写的方式打开文件,如果文件存在会被覆盖, 如果不存在,则会创建文件
a 以追加的方式打开文件,如果文件存在,则移动文件指针到文件结尾,开始写入
readline按行读取数据,如果文件很大,那么一次性读取会给内存造成很大压力 因此使用readline
readline 一行一行的读取数据
复制文件案例
# 1. 打开文件
file_read = open("README")
file_write = open("README[复件]", "w")
# 2. 读·写文件
text = file_read.read()
file_write.write(text)
# 3. 关闭文件
file_read.close()
file_write.close()
# 大文件读取方法
# 1. 打开文件
file_read = open("README")
file_write = open("README[复件]", "w")
# 2. 读写文件
while True:
# 读取一行内容
text = file_read.readline()
# 判断是否读取到内容
if not text:
break
file_write.write(text)
# 3. 关闭文件
file_read.close()
file_write.close()
ASCII 有256个字符 美国标准信息交换代码
python2.x 默认使用的ASCII 编码格式 不支持中文
python3.x 默认使用utf-8编码格式
eval函数:eval函数会将字符串当成有效表达式来求值并且返回运算结果
在类中定义__call__方法之后,就可以直接通过 对象名()调用call方法中的内容
实例
# __call__方法的使用
# 创建对象之后 直接使用对象名() 将会执行类中的__call__方法
class Cat(object):
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Cat()
obj()
# ------------------
__call__