文章目录
- 一、面向对象的基本概念
- 1、面向对象
- 2、类和对象
- (1)类
- (2)对象
- (3)定义简单的类
- (4)创建对象
- (5)增加属性
- (6)`dir(函数名)` 查看函数的属性和方法
- ①初始化方法__init__
- ②在初始化方法内部定义属性
- ③形参,使代码更灵活
- ④__del__
- ⑤__str__方法
- 二、面向对象的三大特性
- 1、面向对象封装
- 应用1:Yilia的体重
- 应用2:摆放家具
- 应用3:士兵突击
- (1)身份运算符`is`、`is not`
- (2)私有属性和私有方法
- (3)伪私有属性和私有方法(科普)
- 2、继承
- 3、单继承
- (1)语法:
- (2)与继承有关的专业术语:
- (3)继承的遗传性
- (4)方法的重写
- ①“覆盖”父类的方法
- ②对父类进行“扩展”
- (5)父类的私有属性和私有方法
- 4、多继承
- (1)语法
- (2)多继承的使用注意事项
- python中的MRO——方法搜索顺序
- (3)新式类——以`object`为基类
- 5、多态
- 三、3种“方法”
- 1、实例方法
- 2、类属性和类方法
- (1)类属性和实例属性
- (2)类方法
- (3)静态方法
- 3、综合案例
- 4、实例小结
- 四、单例
- 1、单例设计模式
- 2、__new__方法
- 3、python中的单例
- 五、异常
- 1、异常的概念
- 2、捕获异常
- (1)简单的捕获异常语法
- 3、错误类型捕获
- 4、异常的传递
- 5、抛出raise异常
- 六、模块
- 1、模块的两种导入方式
- 2、模块的搜索顺序
- 3、包package
- 4、发布模块
- 5、安装模块
- 6、卸载模块
- 7、pip安装与卸载第三方模块
- 七、文件
- 1、文件的概念
- 2、文件的基本操作
- 3、文件指针
- 4、打开文件的方式
- 5、按行读取文件内容
- 6、复制文件
- 7、文件 / 目录的常用管理操作
- 8、eval()函数
一、面向对象的基本概念
1、面向对象
面向对象编程(Object Oriented Programming,OOP)
面向过程
没有固定的开发套路,不适合复杂项目的开发
过程 只能执行,没有返回值
函数 不仅能执行,还有返回结果
面向对象
是更大的封装,根据职责在一个对象中封装多个方法。适合复杂项目的开发。
首先确定职责——要做的事情(方法)
根据职责确定 不同的对象,在对象内部封装 不同的方法
根据完成的代码,顺序地让 不同的对象 调用 不同的方法。
2、类和对象
类和对象是面向对象编程的两个核心概念。
类就相当于制造飞机时的图纸,是一个模板,是负责创建对象的。
对象相当于根据图纸制造出来的飞机。
在程序开发中,先有类,再有对象。
类只有一个,对象可以有很多个。不同对象之间属性可能会各不相同。
类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少。
(1)类
类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用。
类名:这类事物的名字,满足大驼峰命名法(例:MyName)。
根据 名词 提炼法 分析 整个业务流程,出现的 名词 ,通常就是找到的类。
属性:这类事物具有什么样的 特征
方法:这类事物具有什么样的 行为
需求中没有涉及到的属性和方法,在设计时不需要考虑。
应用:
Yilia今年18岁,长头发,不会画画、会弹钢琴;
lisa今年17岁,短头发,会画画、不会弹钢琴
因此:
类名:person
属性:name、age、hair
方法:painting()、playing_piano()
(2)对象
对象是由类创建出来的一个具体存在,可以直接使用。
由哪一个类创建出来的对象,就拥有在哪一个类中定义的 属性和方法。
(3)定义简单的类
class 类名:
def 方法1(self, 参数列表):
pass
def 方法2(self, 参数列表):
pass
self:
哪一个对象调用的方法,
self
就是哪一个对象的引用。self
表示当前调用方法的对象自己。调用方法时,程序员不需要传递self
参数。
在方法内部:
通过self.
访问对象的属性
通过self.
调用其他的对象方法
(4)创建对象
对象变量 = 类名()
使用print
输出对象变量,默认情况下是能够输出这个变量引用的对象是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)。
在计算机中,通常使用十六进制表示内存地址。
%d
输出十进制。如print("%d" %a)%x
输出十六进制。如print("%x" %a)
应用:“小猫爱吃鱼,爱喝水”。
# 创建类:
class Cat:
def eat(self, ):
print("小猫爱吃鱼")
def drink(self, ):
print("小猫爱喝水")
# 创建对象:
tom = Cat()
tom.eat()
tom.drink()
print(tom)
#再创建一个对象
lazy_tom = Cat()
lazy_tom.eat()
lazy_tom.drink()
print(lazy_tom)
kitty = lazy_tom
print(kitty)
输出结果:
可以看到tom和lazy_tom的内存地址不一样,kitty和lazy_tom的内存地址是一样的。
(5)增加属性
对象.属性 = "自定义属性名"
日常开发中,不推荐在 类的外部 给对象增加属性。如果在运行时,没有找到属性,程序就会报错。
对象 包含的属性 应该 封装在 类的内部。
# 创建类:
class Cat:
def eat(self):
print("%s爱吃鱼" % self.name) # 哪一个对象调用的方法,self就是哪一个对象的引用。
# 创建对象:
tom = Cat()
tom.name = "tom" # 增加属性
tom.eat()
#再创建一个对象
lazy_tom = Cat()
lazy_tom.name = "懒猫" # 增加属性
lazy_tom.eat()
(6)dir(函数名) 查看函数的属性和方法
生命周期:
一个对象从调用
类名()
创建,生命周期开始
一个对象的__del__
方法一旦被调用,生命周期结束
在对象的生命周期内,可以访问对象属性,或者让对象调用方法
①初始化方法__init__
当使用类名()
创建对象时,会自动 执行 以下操作:
①为对象在内存中 分配空间——创建对象
②为对象的属性 设置初始值——初始化方法__init__
(这是对象的内置方法,专门用来定义一个类有哪些属性)
class Cat:
def __init__(self):
print("这是初始化方法")
# 使用类名创建对象,自动初始化
tom = Cat()
②在初始化方法内部定义属性
在__init__
方法内部 使用self.属性名 = 属性的初始值
就可以 定义属性。定义属性之后,再使用Cat
类创建的对象,都会拥有该属性。
class Cat:
def __init__(self):
print("这是初始化方法")
self.name = "tom"
def eat(self):
print("%s爱吃鱼" % self.name)
# 使用类名创建对象
tom = Cat()
tom.eat()
③形参,使代码更灵活
在上一个代码中,Cat的名字固定为Tom,这会使得新建的对象的name都不能变化,很不方便。因此,使用形参:
class Cat:
def __init__(self, new_name):
print("这是初始化方法")
self.name = new_name
def eat(self):
print("%s爱吃鱼" % self.name)
# 使用类名创建对象
tom = Cat("Tom")
tom.eat()
lazy_cat = Cat("kitty")
lazy_cat.eat()
④__del__
当一个对象被从内存中销毁前,会 自动 调用 __del__
,让对象在销毁前再做一些事情。
class Cat:
def __init__(self, new_name):
print("这是初始化方法")
self.name = new_name
def eat(self):
print("%s爱吃鱼" % self.name)
def __del__(self):
print("%s 拜拜" % self.name)
# 使用类名创建对象
tom = Cat("Tom")
tom.eat()
print("*" * 50)
因为tom是一个全局变量,所以执行完所有代码后才会执行__del__
删除tom。如果提前用del
语句删除tom,就会先执行__del__
再接着执行下面的程序。
在上述程序中加入一句del tom
:
tom = Cat("Tom")
tom.eat()
del tom
print("*" * 50)
再次执行:
⑤__str__方法
必须返回一个字符串
class Cat:
def __init__(self, new_name):
print("这是初始化方法")
self.name = new_name
def eat(self):
print("%s爱吃鱼" % self.name)
def __del__(self):
print("%s 拜拜" % self.name)
def __str__(self):
return "我是小猫【%s】" % self.name
# 使用类名创建对象
tom = Cat("Tom")
print(tom)
二、面向对象的三大特性
1、面向对象封装
封装 是面向对象编程的一大特点,是面向对象编程的第一步(根据职责 将属性和方法封装到一个抽象的类中)。
外界使用类创建对象,然后让对象调用方法。对象方法的细节 都被封装在类的内部
应用1:Yilia的体重
Yilia体重47公斤,dudu体重85公斤。每次跑步会减肥0.5公斤,每次吃东西增加1公斤。
class Person:
def __init__(self, name, weight):
self.name = name
self.weight = weight
def __str__(self):
return "我的名字叫%s,体重是%.2f公斤" % (self.name, self.weight)
def run(self):
print("%s的体重是%.2f" % (self.name, self.weight))
self.weight -= 0.5
def eat(self):
print("%s的体重是%.2f" % (self.name, self.weight))
self.weight -= 1
Yilia = Person("Yilia", 47.0)
Yilia.run()
Yilia.eat()
print(Yilia)
dudu = Person("dudu", 85.0)
dudu.run()
dudu.eat()
print(dudu)
输出:
在对象的方法内部,可以直接访问对象的属性。Yilia和dudu两个对象的体重数据是互不影响的。
应用2:摆放家具
要求:
①房子有 户型、总面积 和 家具名称列表。新房子没有任何家具。
②家具HouseItem有名字 和 占地面积,其中:席梦思bed占地4平米
衣柜chest占地2平米
餐桌table占地1.5平米③将以上三件 家具 添加到 房子 中。
添加家具时要判断家具的面积是否超过剩余面积,如果超过,提示不能添加这件家具。
④打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表
分析:
有两个名词,“房子”和“家具”,因此有两个类。
先开发家具类,因为家具类比较简单,且房子要使用到家具,被使用的类应该先开发。
主程序只负责创建 房子 对象 和 家具 对象,让 房子 对象调用add_item
方法将家具添加到房子中。名词:家具
属性:名字、占地面积形参:名字、占地面积
行为:打印“…占地…平米”
对象:bed、chest、table名词:房子
属性:户型、总面积、剩余面积、房子初始值是空的。形参:户型、总面积
行为1:添置家具(判断家具的面积是否超过剩余面积,如果超过,提示不能添加这件家具。此处涉及if分支,包括面积判断与计算);
行为2、打印房子信息(输出:户型、总面积、剩余面积、家具名称列表)
对象:房子
代码:
class HouseItem:
def __init__(self, name, area):
self.name = name
self.area = area
def __str__(self):
return "[%s]占地%.2f" % (self.name, self.area)
class House:
def __init__(self, Type, square):
self.Type = Type
self.square = square
self.left_square = square
self.HouseItem = []
def __str__(self):
return ("户型:【%s】\n总面积:【%d】 m²\n剩余面积:【%.02f】 m²\n家具名称列表%s"
% (self.Type, self.square, self.left_square, self.HouseItem))
"""
python能够自动地将一对括号内部的代码连接在一起
"""
def add_furniture(self, item):
print("要添加%s m²" % item)
if item.area <= self.left_square:
self.HouseItem.append(item.name)
self.left_square -= item.area
else:
print("sorry, %s的面积超过了剩余面积,摆不下" % item.name)
return
# 创建家具
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("桌子", 1.5)
print(bed)
print(chest)
print(table)
# 创建房子对象
my_home = House("两室一厅", 60)
# 添加家具
my_home.add_furniture(bed)
my_home.add_furniture(chest)
my_home.add_furniture(table)
print(my_home)
输出:
将面积修改之后:
应用3:士兵突击
一个对象的属性,可以是另一个类创建的对象
None
关键字表示 什么都没有,表示一个 空对象,没有方法和属性,是一个特殊的常量。可以将None
赋值给任何一个变量。
题目:
士兵许三多有一把AK47,士兵可以 开火。
枪能够发射子弹,枪装填子弹——增加子弹数量
两个对象:
①士兵
特征(即属性):姓名,枪(新兵没有枪,枪对象可以直接调用封装在“枪”类中的“发射子弹,装填子弹”方法)
行为(即方法):开火(判断是否有枪,喊一声口号,装填子弹,射击)②枪
特征(即属性):型号,子弹数量(初始为0)
行为(即方法):发射子弹,装填子弹
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):
print("%s共有【%s】发子弹" % (self.model, self.bullet_count))
if self.bullet_count <= 0:
print("没子弹了,请装入子弹")
return
self.bullet_count -= 1 # 发射子弹
print(("战斗开始!突突突...\n经过一番激烈的战斗,【%s】还剩【%d】发子弹"
% (self.model, self.bullet_count)))
class Soldier:
def __init__(self, name):
self.name = name
self.gun = None
def fire(self):
# 1、是否有枪
if self.gun is None:
print("%s还没有枪" % self.name)
return
# 2、高喊口号
print("冲啊...%s!" % self.name)
# 3、让枪装填子弹
self.gun.add_bullet(10)
# 4、让枪发射子弹
self.gun.shoot()
# 创建枪对象
AK47 = Gun("AK47")
AK47.add_bullet(5)
AK47.shoot()
# 创建士兵对象
xusanduo = Soldier("许三多")
"""
通过把AK47这把枪给许三多,建立起Gun和Soldier的联系。
"""
xusanduo.gun = AK47
xusanduo.fire()
print(xusanduo.gun)
输出:
如果把枪从许三多手上拿走,也就是注释掉xusanduo.gun = AK47
,程序输出为:
(1)身份运算符is、is not
身份运算符用于 比较 两个对象的 内存地址 是否一致——是否是对同一个对象的引用。
在python中针对None
比较时,建议使用is
is 与 == 区别:
is
用于判断 两个变量引用对象 是否为同一个==
用于判断 引用变量的值 是否相等
(2)私有属性和私有方法
在实际开发中,对象的某些属性或方法 可能只希望 在对象的内部使用,而不希望在外部被访问到
私有属性 就是 对象 不希望公开的 属性
私有方法 就是 对象 不希望公开的 方法
定义方式:
在定义属性或方法时,在 属性名或者方法名前 增加 两个下划线,定义的就是私有属性或者方法。
例如:__age
class Women:
def __init__(self, name):
self.name = name
self.__age = 18
def secret(self):
print("%s的年龄是%d" % (self.name, self.age))
Yilia = Women("Yilia")
Yilia.secret()
print(Yilia.__age)
输出:从结果可以看出,Yilia.secret()
可以正常执行,但print(Yilia.__age)
报错。
由此可知:私有属性在外界不能被直接访问。可以在对象的方法内部直接访问对象的私有属性。
class Women:
def __init__(self, name):
self.name = name
self.__age = 18
def __secret(self):
print("%s的年龄是%d" % (self.name, self.age))
Yilia = Women("Yilia")
Yilia.__secret()
将 方法改为私有__secret
,结果也会报错。
因此可知:私有方法不允许被外界直接访问。
(3)伪私有属性和私有方法(科普)
python中没有真正意义上的私有。
在python开发中 不要使用 这种方式访问对象的私有属性或私有方法。以下内容仅限了解:
将上述报错的语句进行修改,就会发现程序可以正常执行了:
将print(Yilia.__age)
改为print(Yilia._Women__age)
将Yilia.__secret()
改为Yilia._Women__secret()
2、继承
继承
继承可以实现代码的重用,相同的代码不需要重复地编写。
子类 拥有 父类 的所有 方法 和 属性。
单继承
一个子类只有一个父类
多继承
一个子类可以有多个父类
3、单继承
(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("汪汪汪")
#创建一个对象——狗对象
laipi = Dog()
laipi.eat()
laipi.drink()
laipi.run()
laipi.sleep()
laipi.bark()
输出:
(2)与继承有关的专业术语:
Dog
类是Animal
类的子类,Animal
类是Dog
类的父类,Dog
类是Animal
类的继承Dog
类是Animal
类的派生类,Animal
类是Dog
类的父类,Dog
类是Animal
类的派生
(3)继承的遗传性
C
类从B
类继承,B
类又从A
类继承。因此C
类具有B
类和A
类的所有属性和方法。
子类 拥有 父类 以及 父类的父类 中封装的所有 属性和方法。
应用1:
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("I can fly")
#创建一个对象——狗对象
laipi = XiaoTianQuan()
laipi.eat()
laipi.drink()
laipi.run()
laipi.sleep()
laipi.bark()
laipi.fly()
输出:
应用2:已知Animal中有好两个子类:Dog和Cat,现在让XiaoTianQuan继承Cat,观察结果:
class Animal:
def eat(self):
print("吃")
def drink(self):
print("喝")
def run(self):
print("跑")
def sleep(self):
print("睡")
class Cat(Animal):
def bark(self):
print("喵喵喵")
class XiaoTianQuan(Cat):
def fly(self):
print("I can fly")
#创建一个对象——狗对象
laipi = XiaoTianQuan()
laipi.eat()
laipi.drink()
laipi.run()
laipi.sleep()
laipi.bark()
laipi.fly()
输出:
很明显这与我们期望的不符合。也就是说。这个时候,“XiaoTianQuan”不止有“Dog”这个爸爸,也有"Cat"这个爸爸,有两个爸爸。这时,应该把“XiaoTianQuan”改为单继承。
(4)方法的重写
当父类的方法不满足子类需求时,可以对方法进行 重写(override)
重写父类的方法有两种情况:
①“覆盖”父类的方法
如果在开发中,父类的方法实现 和 子类的方法实现 完全不同,就可以使用 覆盖的方式,在 子类中编写 父类的方法实现。相当于 在子类中定义了一个和父类同名的方法并且实现。
重写之后,在运行时,只会 调用子类中重写的方法,而 不再会 调用 父类封装的方法。
应用:将(3)中应用1的代码中,关于哮天犬的部分进行改写:
class XiaoTianQuan(Dog):
def fly(self):
print("I can fly")
def bark(self):
print("叫的跟神一样")
输出:
②对父类进行“扩展”
如果在开发中,子类的方法实现中包含父类的方法实现,父类原本封装的方法实现是子类方法的一部分。就可以使用扩展的方式,在子类中重写父类的方法。
在需要的位置使用super().父类方法
来调用父类方法的执行;
代码其它位置针对子类的需求,编写子类特有的代码实现。
关于super
在python中
super
是一个特殊的类super()
是使用super
类创建出来的 对象
最常使用的场景就是在 重写父类方法时,调用在父类中封装的方法实现。
应用:将(3)中应用1的代码中,关于哮天犬的部分进行改写:
class XiaoTianQuan(Dog):
def fly(self):
print("I can fly")
def bark(self):
#1、针对子类特有的需求 编写代码
print("叫的跟神一样")
#2、使用super()调用原本在父类中封装的方法
super().bark()
#3、其他子类的代码
print("!#$^$%^%^*")
输出:
如果使用 子类调用方法 会出现递归调用,从而出现死循环::
class XiaoTianQuan(Dog):
def bark(self):
XiaoTianQuan.bark(self)
(5)父类的私有属性和私有方法
子类对象 不能 在自己的方法内部,直接 访问父类的私有属性或私有方法。
子类对象 可以 通过父类的共有方法 间接访问到私有属性或私有方法。
私有属性、方法 是对象的隐私,通常用于做一些内部的事情,不对外公开,外界以及子类都不能直接访问。
4、多继承
子类可以有多个父类,并且具有 所有父类 的 属性和方法
如:孩子会继承自己父亲和母亲的特性
(1)语法
class 子类名(父类名1, 父类名2, ...):
pass
应用:
class A:
def test(self):
print("a-test")
class B:
def demo(self):
print("b-demo")
class C(A, B):
pass
#创建子类对象
c = C()
c.test()
c.demo()
输出:
(2)多继承的使用注意事项
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类?
python中的MRO——方法搜索顺序
python中针对 类 提供了一个 内置属性
__mro__
,可以查看 方法 搜索顺序。MRO(method resolution order),主要用于 在多继承时判断方法、属性 调用路径。
例如:print(c.__mro__)
输出结果:(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
按照__mro__输出顺序从左向右依次查找。如果找到将不再继续搜索,如果找到最后一个类,还没有找到方法,程序报错。
开发时应该尽量避免这种容易产生混淆的情况。如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承。
应用:
class A:
def test(self):
print("a-test")
def demo(self):
print("a-demo")
class B:
def test(self):
print("b-test")
def demo(self):
print("b-demo")
class C(A, B):
pass
# 创建子类对象
c = C()
c.test()
c.demo()
print(C.__mro__)
输出:
按照C→A→B顺序查找。
(3)新式类——以object为基类
object
是python为所有对象提供的 基类,提供有一些内置的属性和方法,可以使用dir
函数查看。如果没有指定父类,会默认使用object
作为该类的基类。
class 类名(object):
pass
5、多态
不同的子类对象 调用 相同的父类方法,产生不同的执行效果,增加代码的灵活度。
以 继承 和 重写父类方法 为前提。
是调用方法的技巧,不会影响到类的内部设计。
应用
需求:
①在
Dog
类中封装方法game
:普通狗只会简单地玩耍。
②定义XiaoTianQuan
继承Dog
,并重写game
方法:哮天犬需要在天上玩耍。
③定义Person
类,并且封装一个和狗玩的方法:在方法内部,直接让狗对象调用game
方法。
分析:
class Dog(object):
def __init__(self, name):
self.name = name
def game(self):
print("%s在蹦蹦跳跳地玩耍" % self.name)
class XiaoTianQuan(Dog):
def __init__(self, name):
self.name = name
def game(self):
print("%s在天上玩耍" % self.name)
class Person(object):
def __init__(self, name):
self.name = name
def play_with_dog(self, dog):
dog.game()
print("%s和%s在玩耍" % (self.name, dog.name))
# 创建一个狗对象
laipi = XiaoTianQuan("赖皮")
xiaohui = Dog("小灰")
# 创建一个人对象
Yilia = Person("Yilia")
# 让人和狗玩耍
Yilia.play_with_dog(laipi)
Yilia.play_with_dog(xiaohui)
输出:
三、3种“方法”
1、实例方法
1、面向对象开发中,第一步是设计类。
2、使用类名()
创建对象,创建对象的动作有两步:在内存中为对象分配空间
调用初始化方法__init__
为对象初始化3、对象创建后,内存中就有了一个对象的实实在在的存在——实例。
实例化方法:对象名.方法名()
创建出来的 对象 叫做 类的实例
创建对象的 动作 叫做 实例化
对象的属性 叫做 实例属性
对象调用的方法 叫做 实例方法
在程序执行时:
对象各自拥有自己的实例属性
调用对象方法,可以通过self.
访问自己的属性
调用自己的方法
结论:
每一个对象 都有自己 独立的内存空间,保存各自不同的属性;
多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用 传递到方法的内部
2、类属性和类方法
类是一个特殊的对象
python中一切皆对象
class AAA:
定义的类属于 类对象obj1 = AAA()
属于实例对象在程序运行时,类 同样会被 加载到内存;
在python中,类 是一个特殊的对象——类对象;
在程序运行时,类对象 在内存中 只有一份,使用 一个类 可以创建出 很多个对象的实例;
除了封装 实例的属性和方法 外,类对象还可以 拥有自己的属性和方法——类属性、类方法。通过 类名 的方式可以 访问 类属性 或者 调用 类方法
类名.类属性
,类名.类方法
注:如果使用对象.类属性 = 值
赋值语句,只会给对象添加一个属性,而不会更改原有 类属性的值。因此,不推荐使用对象.类属性
的方法。
(1)类属性和实例属性
类属性:给 类对象 定义的属性,用来记录 与这个类相关 的特征,不会记录 具体对象的特征。使用 赋值语句 在class
关键字下方定义 类属性。
应用:定义一个工具类,每件工具都有自己的name
,请说出这个类创建了多少工具对象。
class Tool(object):
# 定义类属性
count = 0
def __init__(self, name):
self.name = name
Tool.count += 1 # 通过`类名.count`调用类属性,让类属性+1
# 创建对象
futou = Tool("斧头")
liandao = Tool("镰刀")
chutou = Tool("锄头")
#输出工具总数
print(Tool.count)
输出:
在python中属性的获取存在一个向上查找机制
(2)类方法
针对 类对象 定义的方法。在 类方法 内部可以直接 访问类属性 或者调用其他的类方法。
语法:
@classmethod
def 类方法名(cls):
pass
类方法需要用装饰器@classmethod
来标识,告诉解释器这是一个 类方法。
1、类方法的第一个参数应该是
cls
。由哪一个类调用的方法,方法内的
cls
就是哪一个类的引用。
这个参数和 实例方法 的第一个参数self
类似。提示使用其他名称也可以,调用方法时,不需要传递cls
参数。2、在方法内部:可以通过
cls.
访问类的属性,也可以通过cls.
调用其他的类方法。
应用:定义一个工具类,每件工具都有自己的name
。在类中封装一个show_tool_count的
的类方法,输出使用当前这个类,创建的对象个数。
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)
# 创建对象
futou = Tool("斧头")
futou.show_tool_count()
liandao = Tool("镰刀")
liandao.show_tool_count()
chutou = Tool("锄头")
chutou.show_tool_count()
输出:
(3)静态方法
在开发时,如果需要在类中封装一个方法,这个方法:
既不需要访问实例属性或者调用实例方法,
也不需要访问类属性或者调用类方法。
这个时候,可以把这个方法封装成静态方法。
语法:
@staticmethod
def 静态方法名():
pass
静态方法需要用装饰器@staticmethod
来标识,告诉解释器这是一个静态方法。通过类名.
,调用静态方法,不需要创建 对象。
应用:
class Dog(object):
@staticmethod
def bark(): # 注意这里括号里没有self
print("汪汪汪")
# 调用静态方法
Dog.bark()
输出:
3、综合案例
class Game(object):
#定义类属性
top_score = 0 # 历史最高分
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)
#查看帮助信息
Game.show_help()
#查看历史最高分
Game.show_top_score()
#创建对象
Yilia = Game("Yilia")
Yilia.start_game()
输出:
4、实例小结
实例方法——方法内部需要访问 实例属性(使用类名.
访问类属性)。
类方法——方法内部 只 需要访问 类属性
静态方法——方法内部 不需要 访问实例属性和类属性
如果方法内部既需要访问 实例属性,有需要访问 类属性,应该定义成什么?答:实例方法。类只有一个,在实例方法内部可以使用类名.
访问 类属性
。
四、单例
1、单例设计模式
设计模式:
是前人工作的总结和提炼。通常,被人们广泛流传的设计模式都是针对“某一特定问题”的成熟解决方案。使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性。
单例设计模式:
目的——让类创建对象,在系统最终只有唯一的实例,
每一次执行类名()
返回的对象,内存地址是相同的。
应用场景:
音乐播放对象
回收站对象
打印机对象
2、__new__方法
使用 **类名()**创建对象时,python解释器首先会调用__new__
方法 为对象分配空间。__new__
是一个由object
基类提供的内置的 静态方法,主要作用有两个:
①在内存中为对象分配空间,
②返回对象的引用。
python解释器获得对象的引用后,将引用作为第一个参数,传递__init__
方法。
重写__new__
方法的代码非常固定,一定要return super().__new__(cls)
。否则python解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法。注意:__new__
是一个静态方法,在调用时需要 主动传递cls
参数。
class MusicPlayer(object):
def __new__(cls, *atgs, **kwargs): # *表示元组参数,**表示字典参数
#1、创建对象时,new方法会被自动调用
print("创建对象,分配空间")
#2、为对象分配空间
instance = super().__new__(cls)
#3、返回对象的引用
return instance
def __init__(self):
print("播放器初始化")
#创建播放器的对象
player = MusicPlayer()
print(player)
输出:
3、python中的单例
1、定义一个类属性,初始值是None
,用于记录 单例对象的引用
2、重写__new__
方法
3、如果 类属性is None
,调用父类方法分配空间,并在类属性中记录结果
4、返回 类属性中记录的对象引用
应用:
class MusicPlayer(object):
# 类属性
instance = None
#改造__new__方法
def __new__(cls, *args, **kwargs):
#1、判断类属性是否是空对象
if cls.instance is None:
#2、调用父类的方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
#3、返回类属性保存的对象引用
return cls.instance
#创建对象
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
输出:两个对象的内存地址是一样的
在上面这个例子中,存在一个问题:每次执行时,初始化方法会被再次调用。让 初始化动作只被 执行一次,解决方法:
1、定义一个类属性
init_flag
标记是否执行过初始化动作,初始值为Flase
2、在__init__方法中,判断init_flag
,如果为Flase
,就执行初始化动作
3、然后将init_flag
设置为True
4、这样,再次自动调用__init__
方法时,初始化动作就不会被再次执行了。
class MusicPlayer(object):
# 类属性
instance = None
#改造__new__方法
def __new__(cls, *args, **kwargs):
#1、判断类属性是否是空对象
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)
五、异常
1、异常的概念
程序在运行时,如果python解释器遇到一个错误,会 停止程序的执行,并且提示一些错误信息(这个动作称为:抛出rise异常),这就是异常。
程序开发时,很难将所有的特殊情况都处理得面面俱到,通过 异常捕获 可以针对突发事件做集中的处理,从而保证程序的 稳定性和健壮性。
尽管我们按照python的语法规则编写代码了,但是还是有可能报错。如num = int(input("请输入整数:"))
,如果用户输入一个字母,程序就会报错。
2、捕获异常
(1)简单的捕获异常语法
在程序开发中,如果 对某些代码的执行不能确定是否正确,可以增加try
来捕获异常。语法:
try:
尝试执行的代码,即不确定是否能正常执行的代码
except:
出现错误的处理
应用:
try:
num = int(input("请输入整数:"))
except:
print("抱歉,请输入正确的整数")
print("*" * 50)
输出:
3、错误类型捕获
在程序执行时,可能会遇到不同类型的异常,并且需要 针对不同类型的异常,做出不同的相应,这个时候,就需要捕获错误类型的代码了。
捕获未知错误:
在开发时,要 预判到所有可能出现的错误,还是有一定难度的。
如果希望程序 无论出现任何错误,都不会因为python解释器抛出异常而终止,可以再增加一个except
。
语法:
try:
#尝试执行的代码
pass
except 错误类型1:
#针对错误类型1,对应的代码处理
pass
except (错误类型2):
#针对错误类型2,对应的代码处理
pass
except Exception as result:
print("未知错误%s" % result) # result是变量名,可以重新定义为其他名字
else:
#没有异常才会执行的代码
print("尝试成功")
finally:
#无论是都有异常,都会执行的代码
print("无论是都有异常,都会执行的代码")
当python解释器抛出异常时,最后一行错误信息的 第一个单词 就是错误类型。
例如:
num = int(input("请输入整数:"))
result = 8 / num
print(result)
报错:
图中
ZeroDivisionError
就是错误类型。
图中
ValueError
就是错误类型。
将程序完善为:
try:
num = int(input("请输入整数:"))
result = 8 / num
print(result)
except ZeroDivisionError:
print("num不能为0")
except ValueError:
print("抱歉,请输入正确的整数")
except Exception as result:
print("未知错误%s" % result)
输出:
①当输入非零整数时:
②当输入0时:
③当输入不是整数时:
4、异常的传递
当 函数 / 方法 执行 出现异常,会将异常传递给函数 / 方法的 调用一方。如果 传递到主程序,仍然 没有异常处理,程序才会被终止。
在开发中,可以在 主函数 中增加 异常捕获。在主函数中调用的其他函数,只要出现异常,都会传递到主函数的异常捕获中。这样就不需要在代码中,增加大量的异常捕获,能够保证代码的整洁。
def demo1():
return int(input("输入整数:"))
def demo2():
return demo1()
def demo3():
return demo2()
#利用异常的传递性,在主程序中捕获异常。
print(demo3())
5、抛出raise异常
应用场景:
代码执行出错,抛出异常;
根据应用程序特有的业务需求,主动抛出异常。例如:提示用户输入密码,如果长度少于8,抛出异常。
当前函数 只负责 提示用户输入密码,如果密码长度不正确,需要其他的函数进行额外的处理。因此可以抛出异常,由其他需要的函数捕获异常。
python中提供了一个Exception
异常类。在开发时如果满足特定业务需求时,希望抛出异常,步骤为:
①创建一个Exception
的 对象,
②使用raise
关键字抛出 异常对象。
def input_password():
# 1、定义一个input_password函数
pwd = input("请输入密码:")
# 2、如果用户输入长度>8,返回用户输入的密码
if len(pwd) > 8:
return pwd
# 3、如果用户输入长度<8,抛出异常
print("主动抛出异常")
# 创建异常对象,可以使用错误信息字符串作为参数
ex = Exception("密码长度不够")
# 抛出异常的对象
return ex
# 捕获异常
try:
print(input_password())
except Exception as result:
print(result)
输出:
六、模块
模块是python程序架构的一个核心概念。
每一个以扩展名
.py
结尾的python源代码文件都是一个模块
模块名同样也是一个标识符,需要符合标识符的命名规则
在模块中定义的全局变量、函数、类都是提供给外界直接使用的工具
模块好比是工具包,要想使用这个工具包,就需要先导入这个模块
1、模块的两种导入方式
①import导入
是一次性把模块中所有工具全部导入,并且通过模块名 / 别名访问。(同from 模块名 import *
,这种方法在使用 方法 时不用指定 模块名. ,但是这种方法在排查错误时会很麻烦,因此不推荐)
import 模块名1
import 模块名2
导入之后,通过模块名.
使用模块提供的工具——全局变量、函数、类
如果模块的名字太长,就可以使用as
指定模块的名称
import 模块名1 as 模块别名
模块别名应符合大驼峰命名法
②from … import 导入
如果希望从 某一个模块中,导入 部分 工具,就可以使用from...import
的方式。
from 模块名1 import 工具名
导入之后,不需要使用模块名.
,可以直接使用模块提供的工具——全局变量、函数、类。
如果两个模块,存在同名的函数,那么后导入模块的函数,会覆盖掉先导入的函数。开发时import代码应该统一写在代码顶部,这样更容易及时发现错误。一旦发现冲突,可以使用as
关键字给其中一个工具起别名。
2、模块的搜索顺序
python解释器在导入模块时,会:先搜索 当前目录指定模块名的文件,如果有就直接导入;如果没有,再搜索系统目录。
例如:
import random
rand = random.randint(0, 10)
print(rand)
这个时候,如果当前目录下,存在一个random.py
文件,python解释器就会 加载当前目录下的random.py
,而不会加载 系统的random
模块,程序就无法正常执行了。因此,在开发时,给文件起名,不要和 系统的模块文件重名。
python中每一个模块都有一个内置属性__file__
可以查看模块的完整路径。例如:查看random模块
路径:print(random.file)
在导入文件时,文件中所有 没有任何缩进的代码 都会被执行一遍。
例如:
模块_test.py
print(“Yilia爱音乐”)
模块_执行.py
import 模块_test
print(“啦啦啦”)输出:
但实际上,文件被导入时,能够直接执行的代码(也就是不带缩进的代码) 不需要 被执行。因此用__name__
属性,使测试模块的代码(也就是不带缩进的代码)只在测试情况下被运行,而在被导入时不会被执行。
__name__
是python的一个内置属性,记录着一个字符串。
如果是被其他文件导入的,__name__
就是 模块名
如果 是当前执行的程序,__name__
是__main__
例如:
模块_test.py
if __name__ == "__main__":
print(__name__)
print("Yilia爱音乐")
模块_执行.py
import 模块_test
print(“啦啦啦”)输出:
主函数格式:
#导入模块
#定义全局变量
#定义类
#定义函数
#在代码的最下方
def main():
# ...
pass
#根据__name__判断是否执行过下方代码:
if __name__ == "__main__":
main()
3、包package
①包是一个 包含多个模块 的 特殊目录,目录下有一个 特殊的文件__init__.py
。
要在外界使用包中的模块,需要在
__init__.py
中指定 对外界提供的模块列表。
语法:from . import 模块名
from . import 模块名
②包名的 命名方式 和变量名一致,小写字母+_
③使用import 包名
可以一次性导入 包中所有的模块
两种创建“包”的方式:
第一种方式:
第一步:新建目录,并将这个目录命名为Yilia.Directory
。如下图:
然后在这个目录下新建一个名为
__init__
的python文件:
第二种方式:
用这种方式创建出来的包,会自动包含一个
__init__,py
文件。
应用:
Yilia_package
:
__init__,py
from . import send_message
from . import receive_message
send_message.py
def send(text):
print("正在发送%s..." % text)
receive_message.py
def receive():
return "这是来自...的信息"`
包.py
import Yilia_package
Yilia_package.send_message.send("I love you")
txt = Yilia_package.receive_message.receive()
print(txt)
输出:
4、发布模块
如果希望自己开发的模块 分享 给其他人:
①创建一个setup.py
【具体再查阅资料吧…】
from distutils.core import setup
setup(name = “”,
version = “”,
description = “”,
long_description = “”,
author = “”,
author_email = “”,
url = “”,
py_modules = [])
②创建模块$ pythod3 setup.py build
③生成发布压缩包$ pythod3 setup.py sdict
5、安装模块
tar表示解压缩
$ tar zxvf 解压缩的文件名
$ sudo python3 setup.py install
6、卸载模块
文件名.__file__
# 查看文件目录$ sudo rm -r 文件目录
7、pip安装与卸载第三方模块
$ sudo pip3 install 模块名
# 安装
$ sudo pip3 uninstall 模块名
# 卸载
七、文件
1、文件的概念
文件:长期储存在设备上的一段数据
文本文件:使用文本编辑器查看,本质上还是二进制文件。
二进制文件:不是给人直接阅读的,需要使用专门软件查看的软件。计算机只能识别二进制文件
2、文件的基本操作
①打开文件,并返回文件操作对象:open。file = open("文件名")
文件名区分大小写
如果文件 存在,返回文件操作对象
如果文件 不存在,会抛出异常
②读文件到内存:read。file.read()
③写文件:write。file.write("要写的内容")
④关闭文件:close。file.close()
如果忘记关闭文件,会造成系统资源消耗,而且会影响到后续对文件的访问。
3、文件指针
文件指针标记 从哪个位置开始读取数据。
每一次 打开文件 时,通常文件指针会指向 文件的开始位置。
如果使用read
,文件指针会 移向读取内容的末尾。
4、打开文件的方式
open
默认以 只读 方式打开。
f = open(“文件名”, “访问方式”)
频繁地移动文件指针,会影响文件的读写效率,开发中更多的时候会以只读、只写的方式操作文件。因此,r+、w+、**a+**一般不用。
5、按行读取文件内容
read
方法默认把文件的 所有内容 一次性读取到内存。如果文件太大,堆内存的占用会非常严重。
readline
方法可以一次读取一行内容。方法执行后,会把文件指针移动到下一行,准备再次读取。
读取大文件的正确姿势:
file = open("文件名")
while True:
#读取一行内容
text = file.readline()
#判断是否读到内容
if not text: # 如果没有读取到内容
break
#每读取一行,加一个`\n`
print(text, end = "")
#关闭文件
file.close()
6、复制文件
小文件:打开一个文件,读取完整内容后,写入另一个文件。
#打开文件
file_read = open("文件名")
file_write = open("文件名[复件]", "w")
#读写文件
text = file_read.read()
file_write.write(text)
#关闭文件
file_read.close()
file_write.close()
大文件:打开一个文件,逐行读取内容,顺序写入另一个文件。
#打开文件
file_read = open("文件名")
file_write = open("文件名[复件]", "w")
#读写文件
while True:
#读取一行内容
text = file_read.readline()
#判断是否读到内容
if not text: # 如果没有读取到内容
break
file_write.write(text)
#关闭文件
file_read.close()
file_write.close()
7、文件 / 目录的常用管理操作
在 终端 / 文件浏览器 中可以执行常规的 文件 / 目录 管理操作,例如:创建、重命名、删除、改变路径、查看目录内容。
在python中,如果希望通过程序实现上述功能,需要导入os
模块。
文件操作:
目录操作:
文件 / 目录操作都支持 相对路径和 绝对路径。
文本文件的编码格式:python3默认使用UTF-8
编码。
ASCII编码共有256个字符,每一个ASCII占用一个字节。不支持中文;
*-* coding:utf8 *-*
可以让不是utf-8编码的文件 转为 utf-8编码。u"hello世界"
引号前的u
可以告诉解释器,这是一个utf-8编码的字符串。
计算机使用1~6个字节来表示一个UTF-8
(UTF-8是UNICODE的一种特殊格式)。
8、eval()函数
可以将字符串 当成 有效的表达式 来求值 并 返回计算结果。也就是,可以把字符串中的""
去掉,把引号中的内容当成计算式来运算。
例:
input_str = input("请输入算术题:")
output = eval(input_str)
print(output)
输出:
注:不要用eval直接转化“input的结果”