目录
一、类属性和类方法
1.类的结构
把创建出来对象叫做类的实例,也叫做实例对象,创建对象的动作叫做实例化过程,对象所拥有的属性叫做实例属性,对象调用的方法叫做实例方法,每个对象都有独立的内存空间,多个对象的方法在内存空间中只保留一份,在调用方法时传递对象的引用。
类叫做类对象,创建出来的对象叫做实例对象,在程序运行时,类会和函数一样先加载到内存中但不会去执行。
对象的属性和方法可以使用 self.属性 或 self.方法 的形式去进行调用,类的属性和方法可以通过 类名.属性 或 类名.方法 的形式去进行调用
2.类属性
类属性:就是给类对象定义的属性,通过记录跟类相关的特征,不会去记录对象的特征
需求:定义一个工具类,需要利用这个类去创建对象,现在我们需要记录利用这个类创建了多少对象
class Tools:
# 定义类属性,记录工具对象的总数
count = 0
def __init__(self, name):
self.name = name
Tools.count += 1
tool1 = Tools('剪刀')
tool2 = Tools('斧头')
tool3 = Tools('榔头')
print(f'用这个工具类创建了{Tools.count}个工具') # 用这个工具类创建了3个工具
我们为什么不用:tool1.count 或者 tool2.count 或者 tool3.count 去调用count属性呢?
class Tools:
# 定义类属性,记录工具对象的总数
count = 0
def __init__(self, name):
self.name = name
Tools.count += 1
tool1 = Tools('剪刀')
tool2 = Tools('斧头')
tool3 = Tools('榔头')
print(f'用这个工具类创建了{tool1.count}个工具') # 用这个工具类创建了3个工具
我们可以看到当没有定义count的实例属性时,这种方法是可以的,但是:
class Tools:
# 定义类属性,记录工具对象的总数
count = 0
def __init__(self, name):
self.name = name
self.count = 100
Tools.count += 1
tool1 = Tools('剪刀')
tool2 = Tools('斧头')
tool3 = Tools('榔头')
print(f'用这个工具类创建了{tool1.count}个工具') # 用这个工具类创建了100个工具
print(f'用这个工具类创建了{Tools.count}个工具') # 用这个工具类创建了3个工具
当我们定了一个count的实例属性时,tool1.count 或者 tool2.count 或者 tool3.count 去调用count属性只会调用到实例属性,但通过Tools.count方法就可以调用的类属性
3.类方法和静态方法
类方法
class Tools:
# 定义类属性,记录工具对象的总数
count = 0
def __init__(self, name):
self.name = name
Tools.count += 1
@classmethod
def count_tools(cls):
print(f'用这个工具类创建了{cls.count}个工具')
tool1 = Tools('剪刀')
tool2 = Tools('斧头')
tool3 = Tools('榔头')
Tools.count_tools()
count_tools就是一个类方法,在上面加一个classmethod的装饰器就变成了类方法,cls可以换成别的变量名称(习惯用cls),但是他都指向类Tools的引用
静态方法
class Dog:
def __init__(self, name):
self.name = name
def run(self):
print('run')
wangcai = Dog('旺财')
Dog类中的run方法,既没有调用实例属性和实例方法,也没有调用类属性和类方法,这种方法就叫做静态方法,只不过我们通常不写成上面那种形式,而是写成下面这种形式
class Dog:
def __init__(self, name):
self.name = name
@staticmethod
def run():
print('run')
wangcai = Dog('旺财')
需要注意的是:在方法上面加入一个名字是staticmethod的装饰器,()中不需要写self
调用方法:类名.方法名
class Dog:
def __init__(self, name):
self.name = name
@staticmethod
def run():
print('run')
wangcai = Dog('旺财')
Dog.run() # run
4.方法的综合案例
需求:设计一个Game类,属性:定义一个类属性top_score记录游戏的历史最高分,定义一个实例属性player_name记录当前游戏的玩家姓名,方法:静态方法show_helper显示游戏帮助信息,类方法show_top_score显示历史最高分,实例方法start_game开始当前玩家的游戏,主程序步骤:①.查看帮助信息,②.查看历史最高分,③.创建游戏对象,开始游戏
import random
class Game:
top_score = 0
def __init__(self, name):
self.name = name
@classmethod
def show_top_score(cls):
print(f'游戏的最高分是{cls.top_score}')
@staticmethod
def show_help():
print('请消灭全部敌人')
def start_game(self):
print(f'{self.name}开始游戏...')
score = random.randint(0, 100)
if score > Game.top_score:
Game.top_score = score
# 1.查看帮助信息
Game.show_help()
# 2.查看历史最高分
Game.show_top_score()
# 3.创建游戏对象,开始游戏
xiaoming = Game('小明')
xiaoming.start_game()
# 4.查看游戏过后的最高分
Game.show_top_score()
Tips:当我们需要使用类属性时,就创建类方法;当我们需要使用实例属性,就创建实例方法;当我们既不需要使用类属性也不需要访问实例属性就创建静态方法;当急需要使用实例属性有需要使用类属性时,创建实例方法。
二、单例设计模式
由同一个类创建出来的对象在内存中的地址是相同的(例如:无论我们双击多少次回收站只能打开一个回收站界面)
1.__new__方法
__new__方法是为对象分配内存空间的
class MusicPlayer:
def __new__(cls, *args, **kwargs):
pass
def __init__(self):
print('音乐播放器初始化成功!')
mp1 = MusicPlayer()
mp2 = MusicPlayer()
print(mp1) # None
print(mp2) # None
通过上述代码,我们可以看出对象并没有创建成功,这是因为在创建对象时,首先执行new方法为对象分类内存空间,然后对象在拿着内存空间去执行init方法;但是上述代码中我们并没有为对象返回一个内存空间,所以会创建失败。
class MusicPlayer:
def __new__(cls, *args, **kwargs):
return super().__new__(cls)
def __init__(self):
print('音乐播放器初始化成功!')
mp1 = MusicPlayer()
mp2 = MusicPlayer()
'''
音乐播放器初始化成功!
音乐播放器初始化成功!
'''
print(mp1) # <__main__.MusicPlayer object at 0x000001F3A0037DC0>
print(mp2) # <__main__.MusicPlayer object at 0x000001F3A0037DC0>
2.Python中的单例
class MusicPlayer:
# 定义类属性记录记录单例对象的引用
instance = None
def __new__(cls, *args, **kwargs):
# 判断类属性是否已经赋值
if cls.instance is None:
cls.instance = super().__new__(cls)
# 返回类属性中单例的引用
return cls.instance
def __init__(self):
print('音乐播放器初始化成功!')
mp1 = MusicPlayer()
mp2 = MusicPlayer()
'''
音乐播放器初始化成功!
音乐播放器初始化成功!
'''
print(mp1) # <__main__.MusicPlayer object at 0x000001CB44BB3EB0>
print(mp2) # <__main__.MusicPlayer object at 0x000001CB44BB3EB0>
我们定义的类属性记录单例对象的内存地址,如果第一次创建对象,则会给instance对象赋值为创建的对象的内存地址,如果不是第一次创建对象,此时instance的值是记录的第一次创建对象的内存地址,然后将这个内存地址返回给后面创建的对象,这就可以实现创建的对象的内存地址相同,实现了单例。
但是,上述单例设计还存在一个问题:无论我们创建几个对象,应该只执行一次初始化方法,所以我们要对单例进行改进。
class MusicPlayer:
# 定义类属性记录记录单例对象的引用
instance = None
# 定义类属性判断是否执行过初始化动作
init_flag = False
def __new__(cls, *args, **kwargs):
# 判断类属性是否已经赋值
if cls.instance is None:
cls.instance = super().__new__(cls)
# 返回类属性中单例的引用
return cls.instance
def __init__(self):
# 判断是否执行过初始化动作
if not MusicPlayer.init_flag:
print('音乐播放器初始化成功!')
MusicPlayer.init_flag = True
mp1 = MusicPlayer()
mp2 = MusicPlayer()
'''
音乐播放器初始化成功!
'''
print(mp1) # <__main__.MusicPlayer object at 0x000001CB44BB3EB0>
print(mp2) # <__main__.MusicPlayer object at 0x000001CB44BB3EB0>
三、异常
当python接触器碰见代码中的一些错误时会停止运行,并报出一些提示信息,这就是异常
1.异常捕获
我们为什么要进行异常捕获呢?因为有时候我们不想在异常发生时结束程序的运行,而是希望他继续执行后面的语句。
在python代码中我们使用try,except关键字进行异常的捕获
例子:当我们用python代码实现输入被除数和除数,然后求他们的商时,可能会出现几种错误呢?
1.我们输入的不是数字
2.0不能做除数
简单处理:
c = 'hello world'
try:
a = int(input('请输入被除数:'))
b = int(input('请输入除数:'))
c = a / b
except:
print('请输入正确的数字!')
print(c)
上面的代码无论是碰见了第一种错误还是第二种错误都会输出:请输入正确的数字
所以我们可以这么理解:try中的代码就是要进行异常捕获的代码,except中的代码就是无论try中的代码有什么问题都会被执行
如果我们想针对两种不同的问题进行不同的异常捕获呢?
c = 'hello world'
try:
a = int(input('请输入被除数:'))
b = int(input('请输入除数:'))
c = a / b
except ValueError:
print('请输入正确的数字!')
except ZeroDivisionError:
print('0不能做除数!')
except Exception as e:
print('未知错误:', e)
print(c)
这段代码就是对上面的代码进行了改进,except后面可以加异常名称,表示当遇到这种异常就执行下面的代码
当遇到输入的内容不是数字的错误(ValueError)时:输出请输入正确的数字!
当遇到输入的除数是0(ZeroDivisionError)时:输出0不能做除数!
当遇到我们没有规定的异常时:输出未知错误+错误的名称
异常捕获的完整语法
c = 'hello world'
try:
a = int(input('请输入被除数:'))
b = int(input('请输入除数:'))
c = a / b
except ValueError:
print('请输入正确的数字!')
except ZeroDivisionError:
print('0不能做除数!')
except Exception as e:
print('未知错误:', e)
else:
print('没有出现错误!')
finally:
print('异常捕获已结束!')
print(c)
else:没有异常时执行的代码
finally:异常捕获结束时执行的代码(不管有没有异常都会执行的代码)
2.异常的传递
def demo1():
return int(input('请输入一个数字:'))
def demo2():
return demo1()
print(demo2())
当我们输入的不是一个数字时,他会报以下错误:
Traceback (most recent call last):
File "C:/Users/张洲/Desktop/新建文件夹/新建 JetBrains PyCharm.py", line 9, in <module>
print(demo2())
File "C:/Users/张洲/Desktop/新建文件夹/新建 JetBrains PyCharm.py", line 6, in demo2
return demo1()
File "C:/Users/张洲/Desktop/新建文件夹/新建 JetBrains PyCharm.py", line 2, in demo1
return int(input('请输入一个数字:'))
ValueError: invalid literal for int() with base 10: 'asd\\'
traceback:回溯
首先将demo1,demo2函数加载到内存是没有错误的,当执行print(demo2())时,他会调用demo2函数,而demo2函数返回的是demo1函数的调用,demo1函数调用时返回int(input('请输入一个数字:')),但是我们输入的不是数字,所以他会报错,demo1的这条语句有错误,会把错误反馈给他的调用者:函数demo2,然后demo2再把错误返回给他的调用者,然后反馈给我们,所以就会出现上面这种回溯式的报错信息。
那么这种情况怎么进行异常捕获呢?
我们通俗的做法是将异常捕获写在主程序中,而不是写在函数里
def demo1():
return int(input('请输入一个数字:'))
def demo2():
return demo1()
try:
print(demo2())
except ValueError:
print('请输入正确的数字!')
except Exception as e:
print('未知错误:', e)
3.抛出异常
当我们注册账号时,系统会让我们输入要注册的用户名它通常有一定的长度限制,当我们输入的用户名过长或者过短时,他会提醒我们用户名过短或者用户名过长,这就是典型的抛出异常
def input_name():
# 1.提示用户输入用户名
user_name = input('请输入要注册的用户名:')
# 2.判断用户名的长度
if 6 <= len(user_name) <= 12:
return user_name
# 3.创建异常对象
ex = Exception('用户名长度应在6-12位之间!')
# 4.抛出异常
raise ex
input_name()
当我们输入的用户名不满足<=12,>=6时,抛出异常:
Traceback (most recent call last):
File "C:/Users/张洲/Desktop/新建文件夹/新建 JetBrains PyCharm.py", line 16, in <module>
input_name()
File "C:/Users/张洲/Desktop/新建文件夹/新建 JetBrains PyCharm.py", line 13, in input_name
raise ex
Exception: 用户名长度应在6-12位之间!
可以看见这次抛出的异常并不是python规定的异常,而是我们自己规定的
我们可以再结合异常捕获
def input_name():
# 1.提示用户输入用户名
user_name = input('请输入要注册的用户名:')
# 2.判断用户名的长度
if 6 <= len(user_name) <= 12:
return user_name
# 3.创建异常对象
ex = Exception('用户名长度应在6-12位之间!')
# 4.抛出异常
raise ex
try:
user_name = input_name()
except Exception as e:
print('发现错误:', e) # 发现错误: 用户名长度应在6-12位之间!
四、文件的基本操作
打开文件:
f = open('文件名的路径','访问方式')
open函数会返回一个对象,我们用一个变量接收(通常用f),open函数第一个变量是文件的路径,比如存在d盘my_info文件夹中的info.txt文件,我们需要写的参数就是D:\my_info\info.txt
当我们不指定访问方式时,默认以 r(只读)方式打开
访问方式 | 说明 |
r | 以只读方式打开文件,文件的指针将会放在文件的开头,这是默认模式,如果文件不存在,抛出异常 |
w | 以只写方式打开文件,如果文件存在则会被覆盖,如果文件不存在,创建新文件 |
a | 以追加方式打开文件,如果文件已存在,文件指针将会放在文件的结尾,如果文件不存在,创建新文件进行写入 |
r+ | 以读写方式打开文件,文件的指针将会放在文件的开头,如果文件不存在,抛出异常 |
w+ | 以读写方式方式打开文件,如果文件存在会被覆盖,如果文件不存在,创建新文件 |
a+ | 以读写方式打开文件,如果文件存在,文件指针将会放在文件的结尾,如果文件不存在,创建新文件进行写入 |
读取:
读取全部:
打开文件时光标在开头,读取后光标在末尾,此时如果在执行一次read函数,读取不到任何内容
text = f.read()
print(text)
读取一行:
text = f.readline()
print(text)
# 读取大文件
f = open('文件路径', 'w')
while True:
text = f.readline()
if not text:
break
# 文件的每一行结尾已经有了\n,所以输出不需要换行
print(text, end='')
f.close()
打开文件后必须要关闭:
f.close()
案例:复制文件
# 复制小文件
f_read = open('原文件1.txt', 'r')
f_write = open('原文件1[复制].txt', 'w', encoding='utf-8')
text = f_read.read()
f_write.write(text)
f_read.close()
f_write.close()
# 复制大文件
f_read = open('原文件2.txt', 'r')
f_write = open('原文件2[复制].txt', 'w', encoding='utf-8')
while True:
text = f_read.readline()
if not text:
break
f_write.write(text)
f_read.close()
f_write.close()
文件操作的习惯写法:
with open('1.txt', 'w', encoding='utf-8') as f:
f.write('要写入的内容')