面向对象编程

面向对象编程

面向对象编程:
  核心是对象二字,对象就是一个用来盛放数据与功能的容器
  基于该思想编写程序就是创造一个个的容器
  优点:扩展性强
  缺点:编程的复杂度提升  

在程序中,先定义类,后产生对象

类与对象

对象是"容器",用来存放数据与功能的

类也是"容器",用来存放同类对象共有的数据与功能


详情参见:https://www.cnblogs.com/ZhZhang12138/p/14251433.html

封装

详情参见:https://zhuanlan.zhihu.com/p/109310247

1、引入

面向对象编程有三大特征:封装、继承、多态。

封装指的就是把数据与功能整合到一起。

针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实现:隐藏与开放接口

2、隐藏属性

Python的class机制采用双下划线的方式将隐藏属性隐藏起来(设置成私有的),但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成 " _类名__属性名 " 的形式。
class Foo:
    __N = 0 # 变形为_Foo__N

    def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
        self.__x = 10  # 变形为_Foo_x

    def __f1(self): # 变形为_Foo__f1
        print('__f1 run')

    def f2(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
        self.__f1() # 变形为self._Foo__f1


print(Foo.__N) # 报错
# AttributeError: type object 'Foo' has no attribute '__N'

obj = Foo()
print(obj.__x) # 报错
# AttributeError: 'Foo' object has no attribute '__x'

注意

这种变形需要注意的问题是:

1、在类外部无法直接访问双下滑线的属性,但是知道了类名和属性名就可以拼出名字:_类名__属性。然后就可以访问了,例如:Foo._A_N。
    所以说这种操作并没有严格意义上的限制外部访问,仅仅是语法意义上的变形。

2、在类内部是可以直接访问双下滑线开头的属性的,因为在类定义阶段内部双下划綫开头的属性同一发生了变形。

3、变形操作值在类定义阶段发生一次,在类定义之后的赋值操作,不会变形

3、开放接口

隐藏数据属性

将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接的操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格的控制。
class Teacher:
    def __init__(self, name, age):  # 将名字和年纪都隐藏起来
        self.__name = name
        self.__age = age

    def tell_info(self):  # 对外提供访问老师信息的接口
        print('姓名:%s,年龄:%s' % (self.__name, self.__age))

    def set_info(self, name, age):  # 对外提供设置老师信息的接口,并附加类型检查的逻辑
        if not isinstance(name, str):
            print('姓名必须是字符串类型')
            return
        if not isinstance(age, int):
            print('年龄必须是整型')
            return
        self.__name = name
        self.__age = age


t = Teacher('lili',18)
t.set_info('Lili','19')  # 年龄必须是整型
# 年龄必须是整型
t.tell_info() # 查看老师的信息
# 姓名:lili,年龄:18

隐藏函数属性

目的是为了隔离复杂度,例如ATM程序的取款功能,该功能有好多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,
而对于使用者来说,只需要开发取款这个功能接口即可,取余功能我们都可以隐藏起来。
class ATM:
    def __card(self):  # 插卡
        print('插卡')

    def __auth(self):  # 身份认证
        print('用户认证')

    def __input(self):  # 输入金额
        print('输入取款金额')

    def __print_bill(self):  # 打印小票
        print('打印账单')

    def __take_money(self):  # 取钱
        print('取款')

    def withdraw(self):  # 取款功能
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()
obj = ATM()
obj.withdraw()

4、property

property:可以将类中的函数"伪装成"对象的数据属性,对象在访问该特殊属时会触发功能的执行,然后将返回值作为本次访问的结果。
class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height

    @property
    def bmi(self):
        return self.weight / (self.height ** 2)


obj = People('lili', 75, 1.85)
print(obj.bmi ) # 触发方法bmi的执行,将obj自动传给self,执行后返回值作为本次引用的结果
# 21.913805697589478


使用property有效的保证了属性访问的一致性


class Foo:
    def __init__(self, val):
        self.__NAME = val  # 将属性隐藏起来
    @property
    def name(self):
        return self.__NAME
    @name.setter
    def name(self, value):
        if not isinstance(value, str):  # 在设定值之前进行类型检查

            print('%s must be str' %value)
            return
        self.__NAME = value  # 通过类型检查后,将值value存放到真实的位置self.__NAME
    @name.deleter
    def name(self):
        print('Can not delete')
f = Foo('lili')
f.name
# lili
f.name = 'LiLi'  # 触发name.setter装饰器对应的函数name(f,’Egon')
f.name = 123  # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
del f.name  # 触发name.deleter对应的函数name(f),抛出异常PermissionError

继承

详情参见:https://zhuanlan.zhihu.com/p/109331525

                  https://www.cnblogs.com/ZhZhang12138/p/14261563.html#yifengzhuang

1、继承介绍

继承是创建新类的一种方式
新建的类称之为子类或派生类
被继承的类称之为父类,基类,超类

继承的特点:
  子类可以遗传父类的属性
    
通过类的内置属性__bases__可以查看类继承的所有父类

2、继承与抽象

要找出类与类之间的继承关系,需要先抽象,再继承。抽象即总结相似之处,总结对象之间的相似之处得到类,总结类与类之间的相似之处就可以得到父类,如下图所示

 

基于抽象的结果,我们就找到了继承关系

基于上图我们可以看出类与类之间的继承指的是什么’是’什么的关系(比如人类,猪类,猴类都是动物类)。

子类可以继承/遗传父类所有的属性,因而继承可以用来解决类与类之间的代码重用性问题。

3、属性查找

有了继承关系,对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找……
>>> class Foo:
...     def f1(self):
...         print('Foo.f1')
...     def f2(self):
...         print('Foo.f2')
...         self.f1()
... 
>>> class Bar(Foo):
...     def f1(self):
...         print('Foo.f1')
... 
>>> b=Bar()
>>> b.f2()
Foo.f2
Foo.f1

"""
b.f2()会在父类Foo中找到f2,先打印Foo.f2,然后执行到self.f1(),即b.f1(),仍会按照:对象本身->类Bar->父类Foo的顺序依次找下去,在类Bar中找到f1,因而打印结果为Foo.f1

"""
父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的。
>>> class Foo:
...     def __f1(self): # 变形为_Foo__fa
...         print('Foo.f1') 
...     def f2(self):
...         print('Foo.f2')
...         self.__f1() # 变形为self._Foo__fa,因而只会调用自己所在的类中的方法
... 
>>> class Bar(Foo):
...     def __f1(self): # 变形为_Bar__f1
...         print('Foo.f1')
... 
>>> 
>>> b=Bar()
>>> b.f2() #在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
Foo.f2
Foo.f1

4、继承的应用

子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的init覆盖父类的

class Student:
    school = '虹桥校区'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def choose(self):
        print('%s 选课成功' % self.name)


stu1 = Student('jack', 18, 'mael')
stu2 = Student('tom', 23, 'mael')
stu3 = Student('lili', 28, 'femael')


class Teacher:
    school = '虹桥校区'

    def __init__(self, name, age, gender, level):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level

    def score(self):
        print('%s 正在为学生打分' % self.name)


teal1 = Teacher('egon', 18, 'male', 10)
teal2 = Teacher('lxx', 28, 'female', 3)

很明显子类Teacher中init内的前三行又是在写重复代码,若想在子类派生出的方法内重用父类的功能,有两种实现方式:

super():返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,去当前类的父类中找属性,严格依赖继承

class Base:
    school = '虹桥校区'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


# 方法一:“指名道姓”地调用某一个类的函数
class Student(Base):
    school = '虹桥校区'

    def __init__(self, name, age, gender):
        Base.__init__(self,name, age, gender)  # #调用的是函数,因而需要传入self

    def choose(self):
        print('%s 选课成功' % self.name)

# 方法二:super()
# 调用super()会得到一个特殊的对象,
# 该对象专门用来引用父类的属性,
# 且严格按照MRO规定的顺序向后查找
class Teacher(Base):
    school = '虹桥校区'

    def __init__(self, name, age, gender, level):
        super().__init__(name, age, gender)  #   #调用的是绑定方法,自动传入self
        self.level = level

    def score(self):
        print('%s 正在为学生打分' % self.name)


stu1 = Student('jack', 18, 'mael')
stu2 = Student('tom', 23, 'mael')
stu3 = Student('lili', 28, 'femael')


teal1 = Teacher('egon', 18, 'male', 10)
teal2 = Teacher('lxx', 28, 'female', 3)

5、菱形问题

大多数面向对象语言都不支持多继承,而在Python中,一个子类是可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的 Diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),菱形其实就是对下面这种继承结构的形象比喻

A类在顶部,B类和C类分别位于其下方,D类在底部将两者连接在一起形成菱形。

这种继承结构下导致的问题称之为菱形问题:如果A中有一个方法,B和/或C都重写了该方法,而D没有重写它,那么D继承的是哪个版本的方法:B的还是C的?如下所示


class A(object):
    def test(self):
        print('from A')


class B(A):
    def test(self):
        print('from B')


class C(A):
    def test(self):
        print('from C')


class D(B,C):
    pass


obj = D()
obj.test() # 结果为:from B

要想搞明白obj.test()是如何找到方法test的,需要了解python的继承实现原理

6、继承原理

MRO方法

"""
python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下
"""
>>> D.mro() # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

"""
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
"""

所以obj.test()的查找顺序是,先从对象obj本身的属性里找方法test,没有找到,则参照属性查找的发起者(即obj)所处类D的MRO列表来依次检索,首先在类D中未找到,然后再B中找到方法test

PS:

1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,

7、继承顺序

经典类与新式类

"""
在Python2中有经典类与新式类之分,没有显式地继承object类的类,以及该类的子类,都是经典类,显式地继承object的类,以及该类的子类,都是新式类。

Python3中,即使没有显式地继承object,也会默认继承该类
"""


如果继承关系为菱形结构,那么经典类与新式类会有不同MRO,分别对应属性的两种查找方式:深度优先和广度优先。

菱形继承/死亡钻石:一个子类继承的多条分支最终汇聚到一个非object的类上

经典类:深度优先

新式类:广度优先

即使没有直接继承关系,super仍然会按照mro继续往后查找

#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print('from B')
class C(A,B):
    pass

c=C()
c.test() #打印结果:from B


print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

注意

"""
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。
只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,
每个方法也只会被调用一次
(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,
千万不要通过看代码去找继承关系,一定要看MRO列表)
"""

8、python Minins机制

Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系。

class Vehicle:  # 交通工具
    pass


class FlyableMixin:
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
    pass


class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
    pass


class Car(Vehicle):  # 汽车
    pass

# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路
"""
  
可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,而不是Flyable,
  这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),
  这种命名方式就是用来明确地告诉别人(python语言惯用的手法),
  这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。
  所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。
"""

使用Mixin类实现多重继承要非常小心

  • 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀

  • 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类

  • 然后,它不依赖于子类的实现

  • 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)

9、派生与方法重用

子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的__init__覆盖父类的
>>> class People:
...     school='清华大学'
...     
...     def __init__(self,name,sex,age):
...         self.name=name
...         self.sex=sex
...         self.age=age
... 
>>> class Teacher(People):
...     def __init__(self,name,sex,age,title): # 派生
...         self.name=name
...         self.sex=sex
...         self.age=age
...         self.title=title
...     def teach(self):
...         print('%s is teaching' %self.name)
... 
>>> obj=Teacher('lili','female',28,'高级讲师') #只会找自己类中的__init__,并不会自动调用父类的
>>> obj.name,obj.sex,obj.age,obj.title
('lili', 'female', 28, '高级讲师')

很明显子类Teacher中init内的前三行又是在写重复代码,若想在子类派生出的方法内重用父类的功能,有两种实现方式

方法一:“指名道姓”地调用某一个类的函数

>>> class Teacher(People):
...     def __init__(self,name,sex,age,title):
...         People.__init__(self,name,age,sex) #调用的是函数,因而需要传入self
...         self.title=title
...     def teach(self):
...         print('%s is teaching' %self.name)

方法二:super()

"""
调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找
"""
>>> class Teacher(People):
...     def __init__(self,name,sex,age,title):
...         super().__init__(name,age,sex) #调用的是绑定方法,自动传入self
...         self.title=title
...     def teach(self):
...         print('%s is teaching' %self.name)

提示

"""
在Python2中super的使用需要完整地写成super(自己的类名,self) ,而在python3中可以简写为super()。
"""

区别

"""
方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找
"""
>>> #A没有继承B
... class A:
...     def test(self):
...         super().test()
... 
>>> class B:
...     def test(self):
...         print('from B')
... 
>>> class C(A,B):
...     pass
... 
>>> C.mro() # 在代码层面A并不是B的子类,但从MRO列表来看,属性查找时,就是按照顺序C->A->B->object,B就相当于A的“父类”
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,<class ‘object'>]
>>> obj=C()
>>> obj.test() # 属性查找的发起者是类C的对象obj,所以中途发生的属性查找都是参照C.mro()
from B

10、组合

"""
在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。
不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;
而组合则是一种“有”的关系,
比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例
"""

 

class Course:
    def __init__(self,name,period,price):
        self.name=name
        self.period=period
        self.price=price
    def tell_info(self):
        print('<%s %s %s>' %(self.name,self.period,self.price))

class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day
    def tell_birth(self):
       print('<%s-%s-%s>' %(self.year,self.mon,self.day))

class People:
    school='清华大学'
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age

#Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
class Teacher(People): #老师是人
    def __init__(self,name,sex,age,title,year,mon,day):
        super().__init__(name,age,sex)
        self.birth=Date(year,mon,day) #老师有生日
        self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
    def teach(self):
        print('%s is teaching' %self.name)


python=Course('python','3mons',3000.0)
linux=Course('linux','5mons',5000.0)
teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)

# teacher1有两门课程
teacher1.courses.append(python)
teacher1.courses.append(linux)

# 重用Date类的功能
teacher1.birth.tell_birth()

# 重用Course类的功能
for obj in teacher1.courses: 
    obj.tell_info()


此时对象teacher1集对象独有的属性、Teacher类中的内容、Course类中的内容于一身(都可以访问到),是一个高度整合的产物

多态

1、多态

"""
多态指的是一类事物有多种形态

动物有多种形态:人,狗,猪
文件有多种形态:文本文件,可执行文件
"""
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')
import abc
class File(metaclass=abc.ABCMeta): #同一类事物:文件
    @abc.abstractmethod
    def click(self):
        pass

class Text(File): #文件的形态之一:文本文件
    def click(self):
        print('open file')

class ExeFile(File): #文件的形态之二:可执行文件
    def click(self):
        print('execute file')

2、多态性

一、什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)

多态性是指在不考虑实例类型的情况下使用实例

"""
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),
不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。 比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
"""

多态性分为静态多态性与动态多态性

静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:如下
peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()

二、为什么要用多态性(多态性的好处)

1.增加了程序的灵活性
  以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

2.增加了程序额可扩展性
  通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用  

三、鸭子类型

"""
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’

python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象

也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
"""
#二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系
class Txt: #Txt类有两个与文件类型同名的方法,即read和write
    def read(self):
        pass
    def write(self):
        pass

class Disk: #Disk类也有两个与文件类型同名的方法:read和write
    def read(self):
        pass
    def write(self):
        pass

 绑定方法和非绑定方法

"""
类中定义的函数分为两大类:绑定方法和非绑定方法

其中绑定方法又分为绑定到对象的对象方法和绑定到类的类方法。

"""

@classmethod

"""
在类中正常定义的函数默认是绑定到对象的,而为某个函数加上装饰器@classmethod后,该函数就绑定到了类。
"""

@staticmethod

"""
为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法。该方法不与类或对象绑定,类与对象都可以来调用它,但它就是一个普通函数而已,因而没有自动传值那么一说。
"""
"""
绑定方法:
  特点:绑定给谁就应该由谁来调用,谁来调用就会将自己当做第一个参数传入


非绑定方法:
  特点:不与类和对象绑定,意味着谁都可以来调用,但是无论谁来调用就是一个普通函数,没有自动传参的效果

"""
class People:
    def __init__(self,name):
        self.name = name

    # 但凡在类中定义一个函数,默认就是绑定给对象的,应该由对象来调用
    # 会将对象当做第一个参数自动传入
    def tell(self):
        print(self.name)

    # 类中定义的函数被classmothod装饰过,就绑定给类,应该由类来调用
    # 类来调用就会将类当做第一个参数自动传入
    @classmethod
    def f1(cls):
        print(cls)

    # 类中定义的函数被staticmethod装饰过,就成一个非绑定的方法,即一个普通函数,谁都可以调用
    # 无论谁来调用就是一个普通函数,没有自动传参的效果
    @staticmethod
    def f2(x,y):
        pass

p1 = People('egon')
p1.tell()

p2 = People.f1()  # <class '__main__.People'>

p1.f2(1,2)
People.f2(1,2)

总结

绑定方法与非绑定方法的使用:
若类中需要一个功能,该功能的实现代码中需要引用对象则将其定义成对象方法、需要引用类则将其定义成类方法、无需引用类或对象则将其定义成静态方法。

异常处理

1、什么是异常

异常是程序发生错误的信号。程序一旦出现错误,便会产生一个异常,若程序中没有处理它,就会抛出该异常,程序的运行也随之终止。在Python中,错误触发的异常如下:

语法错误

"""
一种是语法上的错误SyntaxError,这种错误应该在程序运行前就修改正确
"""

>>> if  
  File "<stdin>", line 1
    if
     ^
SyntaxError: invalid syntax

逻辑错误

"""
常见的逻辑错误:
"""

# TypeError:数字类型无法与字符串类型相加
1+’2# ValueError:当字符串包含有非数字的值时,无法转成int类型
num=input(">>: ") #输入hello
int(num)

# NameError:引用了一个不存在的名字x
x

# IndexError:索引超出列表的限制
l=['egon','aa']
l[3]

# KeyError:引用了一个不存在的key
dic={'name':'egon'}
dic['age']

# AttributeError:引用的属性不存在
class Foo:
    pass
Foo.x

# ZeroDivisionError:除数不能为0
1/0

2、异常处理

为了保证程序的容错性与可靠性,即在遇到错误时有相应的处理机制不会任由程序崩溃掉,我们需要对异常进行处理,处理的基本形式为

try:
    被检测的代码块
except 异常类型:
    检测到异常,就执行这个位置的逻辑

完整的异常处理基本形式

try:
    被检测的代码块
except 异常类型1:
    pass
except 异常类型2:
    pass
......
else:
    没有异常发生时执行的代码块

3、何时使用异常处理

"""
在了解了异常处理机制后,本着提高程序容错性和可靠性的目的,读者可能会错误地认为应该尽可能多地为程序加上try...except...,这其是在过度消费程序的可读性,
因为try...except本来就是你附加给程序的一种额外的逻辑,与你的主要工作是没有多大关系的。
"""

可预知的

"""
 如果错误发生的条件是“可预知的”,我们应该用if来进行”预防”,如下
"""

age=input('input your age>>: ').strip()
if age.isdigit(): # 可预知只有满足字符串age是数字的条件,int(age)才不会触发异常,
    age=int(age)
else:
    print('You must enter the number')

不可预知的

"""
如果错误发生的条件“不可预知”,即异常一定会触发,那么我们才应该使用try...except语句来处理。例如我们编写一个下载网页内容的功能,
网络发生延迟之类的异常是很正常的事,而我们根本无法预知在满足什么条件的情况下才会出现延迟,因而只能用异常处理机制了
""" import requests from requests.exceptions import ConnectTimeout # 导入requests模块内自定义的异常 def get(url): try: response=requests.get(url,timeout=3)#超过3秒未下载成功则触发ConnectTimeout异常 res=response.text except ConnectTimeout: print('连接请求超时') res=None except Exception: print('网络出现其他异常') res=None return res get('https://www.python.org')

4、扩展

# 断言
l = [111,222,333]
assert len(l) == 3  # 不满足条件则抛出异常
print('执行后续代码')

# raise
raise IndexError('索引错误')  # 主动抛出异常


# 自定义异常
class Permission(BaseException):
    pass
raise Permission('权限错误')
'''
Traceback (most recent call last):
  File "E:\PYTHON\pycharm project\project\day21\异常处理.py", line 14, in <module>
    raise Permission('权限错误')
__main__.Permission: 权限错误

'''

 

posted @ 2021-06-09 20:37  Palpitate~  阅读(28)  评论(0编辑  收藏  举报