一天学会python面向对象,一篇文章就够了 #大量实例 #多图预警

一天学会python面向对象,一篇文章就够了

面向对象基础

  • 面向对象编程 Object Oriented Programming 简写 OOP
  • 面向过程面向对象,是两种不同的编程方式

1)过程和函数

  • 过程是早期的一个编程概念
  • 过程类似于函数,只能执行,但是没有返回值
  • 函数不仅能执行,还可以返回结果

2)面向过程 和 面向对象

1>面向过程(怎么做)

  1. 把完成某个需求的 所有步骤 从头到尾 逐步实现
  2. 根据开发需求,将某些功能独立的代码封装成一个个函数
  3. 最后完成的代码,就是顺序地调用不同的函数
  • 特点:
  1. 注重步骤与过程,不注重职责分工
  2. 如果需求复杂,代码也会变得复杂
  3. 没有固定的套路,开发复杂项目时难度很大

2>面向对象(谁来做)

面向对象是更大的封装,根据职责在一个对象中封装多个方法

  1. 在完成某一个需求前,首先确定职责==>要做的事情(方法)
  2. 根据职责确定不同的对象,在对象内部封装不同的方法
  3. 最后完成的代码,就是顺序地让不同的对象调用不同的方法
  • 特点:
  1. 注重对象和职责,不同的对象承担不同的职责
  2. 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
  3. 需要在面向过程基础上,再学习一些面向对象的语法

游戏中,每个角色的技能职责不同,就很像这一个个对象。它们的技能就是各种方法。

3>类和对象

1.类

  • 是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
  • 特征被称为属性(变量)
  • 行为 被称为方法(函数)
  • 是一个模板,负责创建对象(实例)

2.对象

  • 对象是有类创建出来的一个具体存在,可以直接使用
  • 创建的对象,拥有类的属性和方法
  • 在程序开发中,先有类,后有对象

3.类和对象的关系

  • 类和对象的区别,也类似于 人类某个具体的人的区别
  • 由一个创建的对象可以有很多个
  • 不同的对象之间属性可能会各不相同
  • 中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少

4.类的设计

  1. 分析需求,确定一下,程序中需要包含哪些类
  2. 类的设计三要素
  1. 类名大驼峰命名法
  2. 属性事物的特征
  3. 方法事物的行为
  • 名词提炼法分析整个业务流程,出现的名词,基本就可以作为类名

需求中没有涉及的属性或者方法在设计类时,不需要考虑

  • 翅膀,身体上有羽毛,喜欢
  • 没有翅膀,身体上有狗毛,喜欢尾巴

5.查看方法的函数dir()

  • python中,一切皆是对象
  • dir(标识符/数据)==>查看对象内的所有属性及方法
    提示__方法名__格式的方法是python提供的内置方法/属性
方法名 类型 作用
__new__ 方法 创建对象时,会被 自动 调用
__init__ 方法 对象被初始化时,会被 自动 调用
__del__ 方法 对象被从内存中销毁前,会被 自动 调用
__str__ 方法 返回对象的描述信息print 函数输出使用
__doc__ 方法 对象中的文档说明

4>定义类

  • 定义类
Class 类名:
    
    变量1 =1
    变量2 =2
    
    def 方法1(self,参数列表):
        pass

    def 方法2(self,参数列表):
        pass
  • 创建对象(类的实例化)
对象名(是一个变量) = 类名()
  • 面向对象开发的特点:只负责让对象去工作,而不再关心方法的内部细节
  • 面向对象开发中,引用的概念同样适用。对象变量记录的依然是对象在内存中的地址
print(one_class)  # <__main__.OneClass object at 0x01BAC2D0>

上面的print函数打印了一个对象,其显示的结果是:该对象所属的类 at 内存中的地址(十六进制表示)

  • print("%d" % 变量名) # 输出10进制地址
  • print("%x" % 变量名) # 输出16进制地址

5>self参数

1. 给对象增加属性

  • 在python中,要给对象设置属性,非常容易,!!!不推荐使用!!!(对象属性的封装一个封装在类的内部)
  • 只需要在类的外部的代码中直接通过赋值语句设置一个属性即可
实例名.新属性名 = 属性值
  • **在外部增加对象属性的隐患:**在类外部给对象增加属性,目的肯定是为了使用。为了使用,类里面的方法,肯定就会引用数据(属性),如果忘了在外部传入某属性,那么程序就会报错。
  • 提示:
  • 在日常开发中,对象应该包含哪些属性,应该封装在类的内部

2. self保存了什么

  • 通过单步调试工具,会发现self的内存地址和调用方法的实例对象的内存地址一模一样。说明:

在这里插入图片描述
在这里插入图片描述

  • self 本来就是一个形参,它传入的参数是实例对象
  • 哪一个对象调用的方法,self就是哪一个方法的引用
  • 在方法内部,要访问对象的属性或方法加上个self.即可

6>几个内置方法与对象的生命周期

1.初始化方法__init__

  • 当使用类名()创建一个实例时,会自动执行以下操作:
  1. 为对象在内存中分配空间==>创建对象
  2. 为对象的属性设置初始值==>初始化方法(__init__)
  • __init__是对象的内置方法

__init__方法是专门用来定义一个类具有哪些属性的方法!

class1:

    def __init__(self, 形参1):
    
        self.属性1 = 形参1


# 实例化
实例1 =1(参数1)
  • 如果希望在创建对象的同时,就设置对象的属性,可以对__init__方法进行改造
  1. 把希望设置的属性值,定义成初始化函数的参数
  2. 在方法内部使用 self.属性名 = 形参名 接收外部传递的参数
  3. 在创建对象时,使用 类名(属性1,属性2…) 调用

2.销毁前方法__del__

  • 当一个对象从内存中销毁前,会自动调用__del__方法
  • 一个实例是一个全局变量,所以只有所有程序执行完成时,才会被回收。也就是程序所有操作的最后,才会调用__del__方法。(也可以使用del关键字强制回收,回收时,触发__del__方法)

class VClass:
    "这是一个对象"

    def __init__(self, arg):

        self.arg = arg
        print("%s 初始化成功" % self.arg)

    def __del__(self):

        print("%s 成功被销毁" % self.arg)

A_class = VClass("A对象")

# 打印在终端的结果显示: 分割线在A对象销毁的上方,说明整个程序结束才销毁 A_class 对象
print("=" * 50)

B_class = VClass("B对象")
del B_class

# 打印在终端的结果显示: 分割线在B对象销毁的下方,说明del提前回收了 B_class 对象
print("*" * 50)

3. 生命周期

  • 一个对象从被创建时,生命周期开始
  • 在对象的生命周期内,可以访问对象属性,或者人对象调用方法

4.打印自定义的内容__str__

  • python使用print打印一个对象会返回 <main.类名 object at 实例所在内存地址>
  • 如果希望print打印这个对象时,输入我们自定义的内容,就可以用__str__函数
  • __str__函数返回的对象,必须是一个字符串

class VClass:
    "这是一个对象"

    def __init__(self, arg):

        self.arg = arg
        print("%s 初始化成功" % self.arg)

    def __del__(self):

        print("%s 成功被销毁" % self.arg)

    def __str__(self):

        return "%s 的类和内存地址是机密" % self.arg

A_class = VClass("A对象")
print(A_class)

"""A对象 初始化成功
A对象 的类和内存地址是机密
A对象 成功被销毁"""

3)面向对象封装案例

  • 封装是面向对象编程的一大特点
  1. step1:属性方法 封装到一个抽象的
  2. step2:外界使用创建对象,然后让对象调用方法
  3. 对象方法的细节都被封装类的内部

案例1:球球大作战

需求

  1. 玩家登录游戏会给球球一个名字
  2. 球球的初始质量10
  3. 球球每次达自己一半质量的果子
  4. 球球 加速时,损失20%质量

在这里插入图片描述

事件

玩家【火锅制胜】 连续吃了20个果子,然后被【第一名】追杀了七次,无情地被吃掉

代码实现

class BallGame:

    def __init__(self, player, weight):

        self.palyer = player
        self.weight = weight

        print("【用户名】:%s  加入了游戏 【质量】:%.2d" % (self.palyer, self.weight))

    def __str__(self):

        return "【用户名】:%s  【质量】:%.2d" % (self.palyer, self.weight)

    def __del__(self):

        if self.palyer == "第一名":
            print("【用户名】:%s  【取得了胜利!!!】" % self.palyer)
        
        else:
            print("【用户名】:%s  【被吃掉了!!!】" % self.palyer)

    def eat(self):

        self.weight *= 1.5

        print("【用户名】:%s 【吃了一个果子】 【质量变为】:%.2d" % (self.palyer, self.weight))

    def accelerate(self):

        self.weight *= 0.8

        print("【用户名】:%s 【加速!!!】 【质量变为】:%.2d" % (self.palyer, self.weight))

one = BallGame("第一名", 65000)
fire = BallGame("火锅制胜", 10)

# 定义一个 火锅制胜 连续吃了20个果子,然后被第一名追杀了七次,无情地被吃掉的事件
for i in range(20):
    
    fire.eat()

for j in range(7):

    fire.accelerate()

del fire
  • 其实更规范应该都封装在类里,自己试试

更多案例

疯狂榨汁机
魔法师学院

  • 在定义属性时,如果 不知道设置什么初始值,可以设置为 None
  • None 关键字 表示 什么都没有
  • 表示一个 空对象,没有方法和属性,是一个特殊的常量
  • 可以将 None 赋值给任何一个变量
  • 封装的 方法内部,还可以调用其他已经封装好的方法(无论是自己对象的方法 还是其它对象的方法)

补充:身份运算符 is

  • 身份运算符用于 比较 两个对象的 内存地址 是否一致 —— 是否是对同一个对象的引用

4)私有属性和方法

  • 在开发时,对象某些属性和方法可能值希望在对象内部被使用,而不希望在外部被访问到
  • 定义方式:
  • 属性名或方法名前增加两个下划线,定义的就是私有属性或方法
  • 私有属性在外界不能被直接访问

1>代码示例(注意看注释)

  • 外部直接调用,失败
class female:

    __age = 18

    def __init__(self, name):

        self.name = name

xf = female("小芳")
print("%s 的年龄是 %s" % (xf.name, xf.__age))  # 这里报错了
  • 通过内部方法,正常访问
class female:

    __age = 18

    def __init__(self, name):

        self.name = name

    def secret(self):
        print(("偷看了%s的隐私,她居然 %s 岁了!哈哈哈!" 
            % (self.name, self.__age + 38)))

xf = female("小芳")
xf.secret()  # 偷看了小芳的隐私,她居然 56 岁了!哈哈哈!
  • 私有方法与私有属性同理

2>python私有化原理及破解私有化

不要使用以下方法访问 私有方法及属性

  • python中,并没有真正意义的私有。毕竟是完全开源语法嘛。python中只有伪私有属性伪私有方法

原理及破解:

  • 私有化其实是对名称做了一些特殊处理
  • 处理方式:私有方法/属性前面加上_类名

实例名._类名__属性或方法名

class female:

    __age = 18

    def __init__(self, name):

        self.name = name

xf = female("小芳")
print("%s 的年龄是 %s" % (xf.name, xf._female__age))  # 小芳 的年龄是 18
  • 当输入实例名._类名__时IDEL中会有智能提示有哪些伪私有方法属性。

5)继承

1>什么是继承

  • python 有三大方法,分别是封装 继承多态
  1. 封装: 根据 职责属性 方法 封装 到一个抽象的
  2. 继承: 实现代码的重用,相同的代码不需要重复的编写
  3. 多态: 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
  • 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
  • 子类应该根据职责,封装子类特有的 属性和方法

class 子类(父类):

class Enchanter:

    def fire(self):
        print("小火球!")

class HighEnchanter(Enchanter):

    def high_fire(self):
        print("豪火球之术!!!")


xiaoming = HighEnchanter()
xiaoming.fire()
xiaoming.high_fire()
  • HighEnchanter在以下简称sonEnchanter在以下简称father

sonfather子类fatherson父类sonfather继承
sonfather派生fatherson基类sonfather派生

  • 继承具有传递性,可以应该接一个地继承。(子类的子类,也是其父类的子类)

2>继承方法的重写

  • 重写父类方法有两种情况:
  1. 直接覆盖方法(在子类中定义一个同名方法)
  2. 对父类方法进行扩展

1. 扩展

  • 需要的位置使用 super().父类方法 来调用父类方法的执行

superpython中一个特殊的类
super()就是使用super类创建出来的一个实例

  • 重写父类方法时,调用在父类中封装的方法实现
  • 代码其他的位置针对子类的需求,编写 子类特有的代码实现
class Enchanter:

    def fire(self):
        print("小火球!")

class HighEnchanter(Enchanter):

    def fire(self):
        print("豪火球之术!!!")
        # 继承父类的方法,不只是继承同名方法,不同名也可以
        super().fire()


xiaoming = HighEnchanter()
xiaoming.fire()  # 会显示豪火球之术!!!\r小火球!

2. 使用类名调用其它类的类方法

父类名.方法名(self)

class Enchanter:

    def fire(self):
        print("小火球!")

class HighEnchanter(Enchanter):

    def hfire(self):
        print("豪火球之术!!!")
        # 继承父类的方法
        New.new(self)

class New():

    def new(self):
        print("这是一个新的,无关的类")

xiaoming = HighEnchanter()
xiaoming.hfire()  # 豪火球之术!!! \r 这是一个新的,无关的类

3.继承与私有方法/属性

  • 子类中不能直接访问父类的私有属性/方法
  • 子类可以通过父类的公有方法,间接地访问父类的私有属性/方法(父类的私有本身就是为了给自己调用的,自己的公有方法调用有错嘛?私有也只是为了不被直接访问到,但是公有方法要运行,私有方法要不要正常运行?肯定要啊,否则定义私有方法干嘛,又不用!那我们调用公有方法时,私有方法如果一罢工,公有办法不就变成了个太监,那不行,不完整的。)

3>多继承

  • 单继承: 子类继承自一个父类
  • 多继承: 子类继承多个父类,同时具有所有父类的属性和方法

class 子类名(父类1, 父类2…):

  • 如果父类之间存在同名属性或方法,应该尽量避免使用多继承

python中的MRO(方法搜索顺序)

  • python中针对提供了一个内置属性`__mro__可以查看方法搜索顺序
  • MRO(method resolution order),主要用于多继承时判断 方法、属性 的调用路径
def 子类(父类1, 父类2):......


print(子类.__aro__)
# 终端输出:(<class '__main__.子类'>, <class '__main__.父类1'>, <class '__main__.父类2'>, <class 'object'>)
# 最后一个类叫基类,是所有类的祖宗类
  • 在搜索方法时,按照__mro__输出结果从左至右 的顺序查找的
  • 如果在某个类中 找到了方法,就直接执行,不再搜索后面的类
  • 如果找到最后一个类,还没有找到方法,程序报错

新式类与旧式类

objectpython为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir函数查看

  • 新式类:object为基类的类,推荐使用
  • 经典类: 不以object为基类的类,不推荐使用
  • python3中,若不指定父类,会以基类为父类。(python2中若不指定则不会以基类为父类)

新式类经典类在多继承时,方法的搜索顺序不同

  • 为了保证编写的代码,能够同时在python2python3中运行,在定义类时,如果没有父亲,统一继承自object

class 类名(object):…

6)多态

  • 不同的子类对象调用相同的父类方法,产生不同的执行结果
  • 多态可以增加代码的灵活度
  • 继承重写父类方法为前提
  • 是调用方法的技巧,不会影响到类的内部设计
class Skill(object):

    def __init__(self, name):

        self.name = name

    def conjure(self):
        print("豪火球之术!!!")

class HSkill(Skill):

    def conjure(self):
        print("灭却!火流星")


class Person(object):

    def __init__(self, name):

        self.name = name

    def conjure_skill(self, skill):    
        print("%s 习得了 %s " % (self.name, skill.name))

        skill.conjure()


# skill = Skill("火系法术")
skill = HSkill("火系法术")  # 试着把这行注释掉,把上行取消注释

sama = Person("殿下")
sama.conjure_skill(skill)

  • 这里的Person类,并不关心,自己调用的是哪个类。这也是面向对象开发的一大特性:不注重内部实现。

7)类属性和类方法

1.实例

  • 使用类名() 0创建对象
  • 会先为该对象分配空间
  • 然后调用初始化方法__init__对象初始化
  • 对象创建后,内存中就有了一个对象的实实在在的存在==>实例
  1. 创建出来的对象叫做实例
  2. 创建对象的动作叫做实例化
  3. 对象的属性叫做实例属性
  4. 对象调用的方法叫做实例方法
  • 对象各自拥有自己的实例属性,通过self.来调用自己的属性和方法
  • 注意:
  • 每一个对象都有自己独立的内存空间,保存各自不同的属性
  • 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部(通过self)

2.类是一个特殊的对象

python中,一切皆对象。对象都拥有属性和方法。

  • 实例的属性保存在各自的实例中,实例的方法,保存在中。所以,程序运行时,也会被加载到内存
  • 类对象拥有自己的属性方法,可以通过类名.来调用属性和方法

3.类属性

  • 类属性就是类对象中直接定义的属性
  • 用来记录与相关的特征。不记录具体对象的特征
  • 需要知道创建了多少实例对象?:
class Tools(object):

    # 定义一个类属性
    count = 0

    def __init__(self, name):

        self.name = name
        
        # 每一个实例对象都会调用初始化方法,于是放在这里计数。
        Tools.count += 1

hammer = Tools("锤子")
axe = Tools("斧头")
hoe = Tools("锄头")
sickle = Tools("镰刀")
saw = Tools("锯子")

print("这个类下有%s个实例" % Tools.count)  # 这个类下有5个实例
  • 类名.类属性访问完全没有问题
  • 实例名.类属性访问也可以,但是存在问题。读取值没有问题,但是当不小心使用赋值语句时,会创建一个实例属性,而不是修改类属性。
class Tools(object):

    # 需要知道创建了多少实例对象?
    count = 0

    def __init__(self, name):

        self.name = name
        
        # 每一个实例对象都会调用初始化方法,于是放在这里计数。
        Tools.count += 1

hammer = Tools("锤子")

print("【通过 类 访问类属性】这个类下有%s个实例" % Tools.count)  # 这里会打印出类属性  count = 1
print("【通过 实例 访问类属性】这个类下有%s个实例" % hammer.count)  # 这里也会打印出类属性  count = 1

hammer.count = 250  # 利用赋值语句会创建实例属性
print("【通过 实例 访问类属性】这个类下有%s个实例" % hammer.count)  # 这里不再打印出类属性,而是实例属性  count = 250

4. 类方法

  • 定义类方法
@classmethod
def 类方法名(cls):......
  1. 类方法需要用修饰器@classmethod来高数解释器这是一个类方法
  2. 类方法的第一个参数cls
  3. 可以通过cls.来访问类的属性调用其它类的方法
class Tools(object):

    # 需要知道创建了多少实例对象?
    count = 0

    def __init__(self, name):

        self.name = name
        
        # 每一个实例对象都会调用初始化方法,于是放在这里计数。
        Tools.count += 1

    @classmethod
    def show_count(cls):
        print("这个类下有%s个实例" % cls.count)


hammer = Tools("锤子")

Tools.show_count()

5.静态方法

  • 如果在中的一个方法,即不访问类/实例属性/方法
@staticmethod
def 静态方法名():......
  • 通过 类名. 就可以调用静态方法, 不需要创建对象
  • 静态方法,不需要传递第一个参数
class Statue(object):

    @staticmethod
    def stand():
        print("一座雕像就伫立在那")

# 通过 类名. 就可以调用静态方法, 不需要创建对象
Statue.stand()

6.综合练习

  • 需求:
  1. 有一个游戏==>定义一个游戏类
  2. 它可以显示历史最高分==>定义一个类属性,再用类方法调用
  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, score):

        self.score = score  # 记录此次游戏得分

        print("%s开始游戏,此次得%d" % (self.player_name, self.score))

        # 算出历史最高分,注意,这里并没有cls,调用会报错。直接用类名.
        if self.score > Game.top_score:
            Game.top_score = self.score


xiaoming = Game("小明")
xiaofang = Game("小芳")
xiaowei = Game("小薇")

xiaoming.show_help()  # 查看帮助信息

xiaoming.start_game(66)
xiaofang.start_game(78)
xiaowei.start_game(59)

Game.show_top_score()

  • 注意: 实例属性涉及到对类属性进行更改,直接用的 类名.
  • 实例方法: 需要访问到实例属性/方法
    • 实例方法中,可以用 类名.来访问类属性/方法
  • 类方法: 函数内部只需要访问类属性/方法
  • 静态方法: 方法内部,不需要访问任何属性/方法

8)单例

  • 设计模式:
  • 设计模式前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案。(一言以蔽之,设计模式就是一个套路)
  • 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码的可靠性
  • 单例设计模式:
  • 目的:创建的对象,在系统中只有唯一的一个实例
  • 每一次执行类名()返回的对象,内存地址是相同的
  • 应用场景:
  1. 音乐播放器
  2. 打印机
  3. 回收站

1.__new__方法

  • 在 7)类属性和类方法 1.实例中 ,提到了:
  • 先为对象分配空间 __new__方法
  • 后初始化对象 __init__
  • 使用 类名() 创建对象时 ,python 的解释器首先会调用__new__ 办法为对象分配空间
  • __new__ 是一个由基类提供的内置的静态方法,作用是:
  • 在内存中为对象分配空间
  • python的解释器获得对象的引用后,将引用作为第一个参数,传递给__init__ 方法(就是那个self)

重写__new__方法

  • 重写的步骤是固定的:
class Class(object):

    # 记录第一个被创建对象的引用
    instance = None

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否为空对象
        if cls.instance is None:
            # 2.调用基类的方法,为第一个对象分配空间
            cls.instance = super().__new__(cls)  # 因为new是静态方法,所以要手动传入cls

        # 3.返回类属性保存的对象引用    
        return cls.instance

object1 = Class()
object2 = Class()
print(object1)  # <__main__.Class object at 0x02AD9250>
print(object2)  # <__main__.Class object at 0x02AD9250>
  1. 要想实现单例,需要定义一个类属性(赋值给instance),初始值为None,用于记录单例对象的引用
  2. 重写__new__方法
  3. 如果类属性 is None ,就会调用父类方法为其分配空间,并在类属性中记录结果
  4. 返回类属性中记录的对象引用

2.只执行一次初始化工作

  • 在每次使用类名()创建对象时,`python 的解释器都会自动调用两个方法:分配空间方法和初始化方法
  • 在改造了分配空间方法后,初始化方法还是会被再次调用,那么如何让初始化方法只执行一次?
  • 解决办法:
  1. 定义一个类属性init_flag(初始化旗标),标记是否执行过初始化动作,初始值为False
  2. __init__方法中,判断初始化旗标,若为否则执行初始化动作
  3. 然后将初始化旗标设为True
  4. 再次自动调用初始化方法时,初始化方法就不会被再次执行了
class Class(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 Class.init_flag:  # 如果初始化旗标为否,则执行初始化操作
            return 

        print("初始化方法执行")
            
        Class.init_flag = True


object1 = Class()
object2 = Class()
object3 = Class()

9)异常

  • 程序停止执行并提示错误信息 这个动作,被称为抛出(raise)异常

不需要将所有特殊情况 处理地面面俱到,通过异常捕获 可以针对突发事件做集中的处理,从而保证程序的稳定性和健壮性

1>捕获异常

  • 对某些代码执行不能确定是否正确 ,可以用try关键字来捕获异常
  • 最简单的格式:
try:
    尝试执行的代码
except:
    错误出现时执行的代码
  • 根据错误类型的格式:

2>错误类型捕获

  • 针对不同类型的异常,需要作出不同的响应,就应该用错误类型捕获

语法:except 错误类型

  • 多个错误用元组
try:
    尝试执行的代码
except 错误类型1:
    针对错误类型1,对应的代码处理
    
except (错误类型2, 错误类型3):
    针对错误类型23,对应的代码处理
    
  • 错误类型如箭头所指d的ValueError:
    在这里插入图片描述

3>捕获未知错误

  • 预判所有可能的错误,很难
  • 如果希望程序无论遇到任何错误,都不会因为python解释器抛出异常而终止,就可以使用捕获未知错误(捕获未知错误的语法非常固定)
except Exception as E:
    针对其它错误类型
    print("其它未知错误 %s" % E)
  • exception 是python中针对异常提供的类, as 是用于取别名的关键字,E是随便取的变量名

4>异常捕获的完整语法

try:
    尝试执行的代码

except 错误类型1:
    针对错误类型1,对应的代码处理
    
except (错误类型2, 错误类型3):
    针对错误类型23,对应的代码处理
    
except Exception as result:
    print(result)  # 打印其它错误的错误信息

else:
    没有异常才会执行的代码

finally:
    无论是否有异常,都会执行的代码

  • 只要没有异常,就会执行 else(执行了Except就不会执行else)
  • 无论是否有异常,都会执行的代码

5>异常的传递

  • 异常的传递:函数/方法 执行出现异常,在抛出异常的一瞬间 会把异常传递给函数、方法调用一方
  • 如果传递到主程序 ,仍然没有异常处理 ,程序才会终止
  • 在下方终端输入0:
def test1():
    return 10 / int(input("请输入一个非零数"))

def test2():
    return test1()

test2()
  • 显示: 调用函数的7行出错==>第5行代码出错==>第7行代码出错==>返回一个除零错误
请输入一个非零数0
Traceback (most recent call last):
  File "d:/@py文件/练习.py", line 7, in <module>
    test2()
  File "d:/@py文件/练习.py", line 5, in test2
    return test1()
  File "d:/@py文件/练习.py", line 2, in test1
    return 10 / int(input("请输入一个非零数"))
ZeroDivisionError: division by zero
  • 执行到第二行代码时,异常抛出的一瞬间,传递给了调用函数的这一方,即第5行代码。异常没有得到解决,于是抛出给调用函数的主程序,就是第七行。所以调用它的主程序先报错。
  • 如果出现异常,难道每一级调用都要添加try......except?利用异常的传递性,在主程序捕获异常即可。

6>主动抛出异常

  • 某些特定的业务中:如一般人输入手机号,不等于11位就是错误的。设置密码时,小于8位就是错误的。这时候就需要主动抛出异常

  • 抛出异常:python中提供了Exception类)

  1. 创建一个 Exception对象
    2.使用raise关键字抛出异常对象
  • demo:
def input_passwd():

    passwd = input("请输入密码(等于六位)")

    if len(passwd) == 6:
        return passwd

    # 创建一个错误实例,括号内是提示信息
    ex = Exception("密码长度不等于六位")
    # 抛出错误信息
    raise ex

# 输入123来抛出异常,下面来捕获异常
try:
    print(input_passwd())

except Exception as R:
    print("发生了一个错误【%s】" % R)

"""请输入密码(等于六位)123
发生了一个错误【密码长度不等于六位】"""

10)模块

  • 每个以**.py** 作为扩展名的python源代码都是一个模块
  • 模块名 同样也是一个标识符 ,需要结合标识符的命名规则
  • 在模块中定义的 全局变量、函数、类 都是提供给外界直接使用的工具

1>导入使用模块

  • 导入模块时,虽然可以使用,分隔,导入多个模块,但根据PEP8,每个导入应该独占一行
  • 可以使用as取别名 ,这个别名需要满足大驼峰命名法
  • 通过模块名/别名.来使用模块

2>导入部分模块

  • 可以使用from 模块名 import 工具名,来使用部分模块。导入之后,不需要使用模块名/别名.来使用,可以直接用该方法的名字来使用
  • 如果两个模块 出现同名工具 ,后导入的会覆盖 先导入的。使用别名 就可以避免这种状况
  • (不推荐使用:) 使用from 模块名 import *,可以导入该模块的所有方法,调用时,可以直接使用工具名调用。

3>模块的搜索顺序

  • python 解释器在导入模块的时候,会优先搜索当前目录,当前目录没有时,搜索系统目录
  • 开发时,给文件起名时,不要和系统文件重名
  • python中每个模块都有一个__file__可以 查看模块完整路径
import django

print(django.__file__)

"C:\Users\User1\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\__init__.py"

4>开发原则:每一个被开发出的文件都应该是可以被导入的

  • 在导入文件时,文件中所有没有任何缩进的代码 都会被执行一遍
  • demo:
  • 定义一个Test文件,输入 print(“这是Test这是Test这是Test”)
  • 定义一个main文件,输入 import Test
  • 直接运行输出了: 这是Test这是Test这是Test
  • 文件中,类似上面print这种直接执行的代码,不是向外界提供的工具,不需要被执行
  • 在实际工作中,一个项目由多人开发。在某个模块内部,是需要做内部测试 的,这些内部测试的代码,也不是向外界提供的工具不需要被执行

__name__:兼顾测试和导入两种模式

  • __name__:测试模块的代码 只在测试情况下被运行 ,而在 被导入时不会被执行

  • __name__ 是 Python 的一个内置属性,记录着一个 字符串
  • 如果 是被其他文件导入的,name 就是 模块名
  • 如果 是当前执行的程序 namemain
  • 随便找一个空文件(命名为Test.py):
print(__name__)  # 输出字符串:__main__
  • 再找一个空文件()命名为main.py)
import Test  # __name__起作用,输出了模块名:Test
  • 使用__name__的原理:
# 根据 __name__ 判断是否执行下方代码
if __name__ == "__main__":
    测试使用的代码
  • 在日常开发中,使用__name__的代码很固定:
# 在代码的最下方:编写有一系列的测试代码
def main():
    测试使用的代码
    
# 根据 __name__ 判断是否执行下方代码
if __name__ == "__main__":
    main()

11)包

  • 是一个包含多个模块特殊目录
  • 为什么说是特殊目录呢?
  • 目录下有一个特殊的文件__init__.py
  • 包名的命名方式 和变量名一致,采取小写字母加下划线命名法
  • import 包名可以一次性导入所有模块

  • 需要在__init__.py中指定对外界提供的模块列表
from . import 模块名  # 从当前目录引入模块

发布模块

  • 在需要发布或分享时,回来看这里即可:
  1. 创建 setup.py 文件
  • 以下代码直接复制修改:
from distutils.core import setup

setup(name="包名",
      version="版本号",
      description="描述信息",
      long_description="完整描述信息",
      author="作者",
      author_email="作者邮箱",
      url="主页",
      py_modules=["包名.模块名1(不带py后缀)",
                  "包名.模块名2(不带py后缀)"])  # 选择要分享的模块名
  1. 构建模块
  • shell中(cd 到setup所在目录):

python setup.py build # 如果安装了多个版本python,要发布哪个版本的,就使用哪个版本的解释器

  1. 生成发布压缩包
  • shell中:

python setup.py sdist

  • 生成了压缩包.tar.gz, 需要分享发布时,直接把这个压缩文件分享出去即可

安装模块

  • shell中:

tar -zxvf 压缩包名.tar.gz

  • 解压后的文件夹中,可以看到PKG-INFO文件,使用cat命令查看,可以看到这个压缩包模块的版本作者联系方式等信息(就是我们写setup.py时会写入的信息)

sudo python setup.py install

卸载模块

  • 直接删目录即可(默认会安装到系统目录下)
  • 可以导入需要删除的模块,使用__file__来查看模块文件的位置

安装第三方模块:pip

  • 第三方模块 通常是由知名的第三方团队 开发的 并且被程序员广泛使用python包/模块
  • pip提供了对python包的查找、下载、安装和卸载等功能

sudo pip install 包名/模块名 # 下载
如果有多个版本,py3解释器使用sudo pip3 install
sudo pip uninstall 包名/模块名 # 卸载

12)文件操作

  • 文件 ,就是存储在长期储存设备 上的一段数据
  • 文件将数据长期保存下来,在需要的时候使用
  • 文件是以二进制 的方式保存在磁盘上的
  • 文本文件:本质上还是二进制文件,可以使用文本编辑软件 查看
  • 二进制文件: 保存的文件不是给人直接阅读的,而是提供给其它软件使用的

1>操作文件的固定三步骤

把大象放进冰箱有几步?三步,打开冰箱,把大象放进去,关上冰箱。

  1. 打开文件
  2. 读写文件
  • 将文件内容读入内存
  • 将内存内容写入文件
  1. 关闭文件

2>操作文件的1个函数 3个方法

函数/方法 说明
open 打开文件,并且返回文件对象
read 将文件内容读取到内存
write 将指定内容写入文件
close 关闭文件
  • 后三个方法需要文件对象 来调用
  • open函数的第一个参数是要打开的文件名(文件名区分大小写)
  • 如果文件存在返回该文件
  • 如果文件不存在跳出异常
  • 打开文件之后,要顺手写下关闭文件的代码,然后在中间对文件执行操作
  • read方法可以一次性读入返回 文件的所有内容

3>文件指针

  • 文件指针标记从哪个位置读取数据
  • 第一次打开文件是,指针通常指向文件开头
  • 当执行read方法后,文件指针 会移动到读取内容的末尾

这就是为什么执行完一次读取操作后,再读取一次,会什么也不显示

4>open的可选参数

  • open的第二个参数决定打开文件的方式,不写默认以只读 打开,并且返回文件对象
访问方式 说明
r 以只读方式打开文件。文件的指针将会放在文件的开头,这是默认模式。如果文件不存在,抛出异常
w 以只写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件
a 以追加方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入
r+ 以读写方式打开文件。文件的指针将会放在文件的开头。如果文件不存在,抛出异常
w+ 以读写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件
a+ 以读写方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入
  • 读写 的方式打开文件,会频繁地移动文件指针,从而影响文件的读写效率, 使用尽量使用只读、只写 的方式来操作文件

5>readline 和 readlines

  • readline 返回一行内容(如果使用遍历,会一个一个字蹦)
file = open("文件名")

while 1:
    line_text = file.readline()

    if not line_text:
        break
        
    print(line_text)

file.close()
  • readlines 返回多行显示内容
file = open("文件名")

for text in file.readlines()
    print(text)

file.close()

6>小文件复制

file_r = open("源文件名", "r")
file_w = open("目标文件名", "w")

text = file_r.read()
file_w.write(text)

file_r.close()
file_w.close()

7>文件/目录的常用管理操作

  • 终端/文件浏览器 中可以执行常规的文件/目录 管理操作
  • python中、要实行这些操作,需要导入os模块
  • 关于路径:绝对路径和相对路径 皆可
  • 文件操作:
方法名 说明 示例
rename 重命名文件 os.rename(源文件名, 目标文件名)
remove 删除文件 os.remove(文件名)
  • 目录操作:
方法名 说明 示例
listdir 目录列表 os.listdir(目录名)
mkdir 创建目录 os.mkdir(目录名)
rmdir 删除目录 os.rmdir(目录名)
getcwd 获取当前目录 os.getcwd()
chdir 修改工作目录 os.chdir(目标目录)
path.isdir 判断是否是文件 os.path.isdir(文件路径)

8>文本文件的编码格式

  • 文本文件存储的内容是基于 字符编码 的文件,常见的编码有 ASCII 编码,UNICODE 编码等
  • utf-8下,绝大多数中文都是使用3个字节的
  • Python 2.x 默认使用 ASCII 编码格式
  • Python 3.x 默认使用 UTF-8 编码格式
  • 如何让python 2.x使用中文?

在 Python 2.x 文件的 第一行 增加以下代码,解释器会以 utf-8 编码来处理 python 文件(官方推荐使用的方法)

# - coding:utf8 -
也可以使用 # coding=utf8

  • 即使在p2中,使用了utf-8编码。在遍历字符串时,它处理起中文字符仍然是一个字节一个字节做切片处理的。

在定义的字符串前加个u(告诉解释器这个字符串是utf-8编码的)
如:u"你好python"

9>eval将字符串变为有效的表达式

  • eval函数可以将字符串去引号化(如果里面是表达式会进行计算):

例如:evil("[list]")==>[list]
eval(“1 + 1”)==>2

不要使用eval直接转换input的结果

  • 提示input时,输入下面一行代码会怎样?(system的参数就是直接执行的终端命令)
__import__('os').system('终端命令')
  • 上面这段代码等价于:
import os

os.system("终端命令")
发布了48 篇原创文章 · 获赞 30 · 访问量 4658

猜你喜欢

转载自blog.csdn.net/weixin_44925501/article/details/103220595