面向对象

面向对象编程

Python全面采用了面向对象的思想,是真正的面向对象的编程语言.一切皆为对象

  • OOP(Object oriented programming)面向对象
  • 一种针对大型软件程序的编程思想
  • 特点
    • 扩展性强,可读性好
    • 使编程就和堆积木一样
    • 将数据(属性)和操作方法(函数)全部封装到对象中
    • 组织代码的方式更加接近人的思维

面向对象与面向过程

  • 区别

    • 面向过程思维

      • "执行者"思维,适合编写小规模程序
      • 我们首先思考"按照什么步骤"实现,一步一步最终完成,适合一些简单的事情,不需要协作关注"程序的逻辑流程"
      • 例如:
      • 开车 (发动车->挂挡->踩油门->走喽)
      • 做饭 (买菜->洗菜->切菜->开火炒菜->开饭了)
    • 面向对象思维

      • "设计者"思维,适合编写大型程序

      • 更加关注"对象之间的关系"

      • 我们首先思考"怎么设计这个东西"

      • 然后我们把这个东西拆分成一个个的物体object(对象)

      • 然后通过分工协作来完成,每个类完成一个部分

      • 例如造车\飞机\轮船\火箭,我们必须先要确定这些复杂的设备是由什么构成的。

        汽车
        发动机 ->发动机厂生产
        轮胎 ->轮胎厂生产
        座椅 -> 座椅厂生产
        挡风玻璃 ->玻璃厂生产

  • 相同点

    • 都是解决问题的思维方式
    • 都是代码的组织方式
    • 人天然就有的能力,一点都不神奇小朋友都能搞明白的问题
  • 配合

    • 宏观面向对象,微观面向过程
    • 简单问题还得面向过程
    • 复杂问题,就得面向对象了
    • 面向对象是宏观把握,从整体上分析系统.
    • 具体到实现部分的微观操作(方法)需要使用面向过程的思路去处理
    • 面向对象和面向过程其实是相辅相成,面向对象离不开面向过程

面向对象哲学

一切皆对象

对象是数据和操作的封装

  • 数据:value ()属性,人类(年龄,性别 ,姓名,身高)
  • 操作:行为,功能,技能(方法)

对象是独立的,但是对象之间可以相互作用

目前面向对象是最接近人类认知的编程范式。

面向对象的三要素

1.封装

  • 组装:将数据和操作组装到一起。
  • 隐藏数据:对象只暴露一些接口,通过接口访问对象。
    • 例如:汽车驾驶员通过方向盘、油门、刹车、挂挡来操作汽车,可以不需要了解汽车的机动原理。

2.继承

  • 多复用,继承来就不需要自己写了。
  • 多继承少修改,OCP原则,使用继承改变,体现个性。

3.多态

  • 鸭子类型实现多态,一个动物,走起路来像鸭子,叫声也想鸭子,那么我们就让我它是鸭子。

  • 面向对象编程最灵活的地方,动态绑定

  • 子类继承父类后,同样的方法实现不同的功能。

  • json dump()load() dumps() loads()

  • pickle dump() load() dumps() loads()

类的定义

1.类的概念

  • 类是对象的模板(描述)
  • 是对一群具有相同特征或行为的事物的一个统称,是抽象的,不能直接使用(就好比,飞机图纸不能直接飞上天)
  • 特征:被称为属性
  • 行为:被称为方法
  • 类 就相当于制造飞机时的图纸,是一个模板,是负责创建对象的

class 类名:定义属性或方法

class 类名:
    语句块

1.必须使用class关键字

2.类名必须是使用大写驼峰命名

  • 大驼峰命名法:
    • 1.每个单词的首字母大写
    • 2.单词与单词之间没有下划线

3.类名:满足这类事物的名字

  • 类名的确定: 名词提炼法
  • 分析整个业务流程,出现的名词,通常就是找到的类名

4.属性: 这个类创建出来的对象有什么样的特征

5.方法:这个类创建出来的对象有什么样的行为

  • 方法的定义格式和函数的几乎一样
  • 区别在于第一个参数必须是self

6.类定义完成后,就产生了一个类对象,绑定到了ClassName上了。

class MyClass:
    """
    这是一个类
    """
    x = "abc"  # 类属性

    def foo(self):  # 类属性foo,也是方法
        return "My Class"
    
print(MyClass.x)
print(MyClass.foo)
print(MyClass.__doc__)
class Student(object):

    # __init__是一个特殊方法用于在创建对象时进行初始化操作
    # 通过这个方法我们可以为学生对象绑定name和age两个属性
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def study(self, course_name):
        print('%s正在学习%s.' % (self.name, course_name))

    # PEP 8要求标识符的名字用全小写多个单词用下划线连接
    # 但是部分程序员和公司更倾向于使用驼峰命名法(驼峰标识)
    def watch_movie(self):
        if self.age < 18:
            print('%s只能观看《熊出没》.' % self.name)
        else:
            print('%s正在观看岛国XXX电影.' % self.name)
            
def main():
    # 创建学生对象并指定姓名和年龄
    stu1 = Student('小明', 20)
    # 给对象发study消息
    stu1.study('Python程序设计')
    # 给对象发watch_av消息
    stu1.watch_movie()
    stu2 = Student('王大锤', 15)
    stu2.study('思想品德')
    stu2.watch_movie()


if __name__ == '__main__':
    main()            

元类:创建类的类

类:创建对象的对象

对象

对象的产生

  • 简单数据 – 数据量过大
  • 数组/列表 – 单一类型数据
  • 结构体 – 不同类型数据
  • 对象 – 将不同类型数据和方法放在一起

对象包含

​ 1.id(身份识别码)
​ 2.type(对象的类型)
​ 3.value(对象的值)
​ (1)属性 attribute
​ (2)方法 method
​ 我们要通过类创建好实例对象后,才能使用类定义的实例属性和方法.
​ 函数和类也是对象,属于python的一等公民
​ 1.可以赋值给一个变量
​ 2.可以添加到列表,元组,集合,字典对象中
​ 3.可以作为参数传递给函数
​ 4.可以作为函数的返回值

属性和方法

self

​ 在类中定义的方法必须有个额外的第一个参数,就是self,表示创建的类实例本身
​ 属性就是在类中定义的变量

  • 实例属性

    • 从属于对象的属性也称实例变量

    • 一般在__init__()中定义

    • self.实例属性=初始化

    • 调用

    • 创建实例对象后

    • obj = 类名()

    • obj.实例属性=值

    • 类内部 self.实例属性名

  • 私有属性

    • 实现"封装",隐藏数据,只能在类的内部调用
    • 两个下滑线__开头的属性是私有属性(private),其他为公共属性(public).
    • 不是特别严格,主要靠自觉
  • 类属性

    • 属于类的属性,所有对象共享
    • 定义
      • 使用类名.类变量名来读写
    • 特殊属性
      • obj.__dict__对象的属性字典
      • obj.__class__对象的所属的类
      • class.__bases__类的基类元组(多继承)
      • class.__base__类的基类
      • class.__mro__类的层次结构
      • class.__subclasssess__()子类列表
  • 方法

    • 方法是行为,是被所有对象共享的.
    • 特殊方法
      • 创建函数__new__()用于创建对象,一般不需要我们自定义
      • 构造函数__init__()对象的初始化,对象建立后,自动执行,初始化当前对象的相关属性,无返回值
      • 析构函数__del__()当类被销毁时,执行的操作。
      • 回调函数__call__()表示对象可以和函数一样被调用.
      • 对象描述__str__()__repr__()
# __new__() 方法是在类准备将自身实例化时调用; 
def __new__(cls,*args,**kwargs):
    return object.__new__(cls)
# 对象建立后,自动执行,初始化当前对象的相关属性,无返回值
def __init__(self,参数列表):
 # 需要被初始化的属性或方法
 pass
# 析构函数
def __del__(self):
    pass
# 当类被销毁时,执行的操作
# 一般用来释放对象占用的资源
# 如打开的文件或链接的网络
# 一般不用自定义析构函数

垃圾回收机制

python实现自动的垃圾回收机制,当对象没有被引用时(引用数为0),由垃圾回收器调用__del__方法
del 销毁对象时,会调用__del__方法

回调函数__call__()

表示对象可以和函数一样被调用.
调用函数本质是调用了__call__方法
在Python中,函数其实是一个对象,这个对象的类中定义了__call__()
案例

对象描述__str__()__repr__()

用于描述对象的信息,也就是print(对象)的结果

Python 魔法方法

基础:

如果你想…所以,你写…Python调用…
初始化一个实例x = MyClass()x.__init__()
作为一个字符串的"官方"表示repr(x)x.__repr__()
作为一个字符串str(x)x.__str__()
作为字节数组bytes(x)x.__bytes__()
作为格式化字符串format(x, format_spec)x.__format__(format_spec)
  • __init__()方法在创建实例后调用.如果你想控制创建过程,请使用__new__()方法
  • 按照惯例, __repr__() 应该返回一个有效的Python表达式的字符串
  • __str__()方法也被称为你的print(x)

迭代相关

如果你想…所以,你写…Python调用…
遍历一个序列iter(seq)seq.__iter__()
从迭代器中获取下一个值next(seq)seq.__next__()
以相反的顺序创建一个迭代器reversed(seq)seq.__reversed__()
  • __iter__()无论何时创建新的迭代器,都会调用该方法.
  • __next__()每当你从迭代器中检索一下个值的时候,都会调用该方法
  • __reversed__()方法并不常见.它需要一个现有序列并返回一个迭代器,该序列是倒序的顺序.

属性

如果你想…所以,你写…Python调用…
得到一个属性x.my_propertyx.__getattribute__('my_property')
获得一个属性x.my_propertyx.__getattr__('my_property')
设置一个属性x.my_property = valuex.__setattr__('my_property', value)
删除一个属性del x.my_propertyx.__delattr__('my_property')
列出所有属性和方法dir(x)x.__dir__()
  • 如果你的类定义了一个__getattribute__()方法,Python将在每次引用任何属性或方法名时调用它.
  • 如果你的类定义了一个__getattr__()方法,Python只会在所有普通地方查找属性后调用它.如果一个实例x定义了一个属性 color, x.color将不会调用x.__getattr__('color'); 它将简单地返回已经定义的x.color值.
  • __setattr__()只要你为属性指定值,就会调用该方法.
  • __delattr__()只要删除属性,就会调用该方法.
  • __dir__()如果您定义一个__getattr__() 或者 __getattribute__() 方法,该方法很有用.通常情况下,调用dir(x)只会列出常规属性和方法.

getattr()和__getattribute__()方法之间的区别很微妙但很重要.

函数类

通过定义__call__()方法,您可以创建一个可调用类的实例 - 就像函数可调用一样.

如果你想…所以,你写…Python调用…
来"调用"像函数一样的实例my_instance()my_instance.__call__()

行为

如果你的类作为一组值的容器 - 也就是说,如果问你的类是否"包含"一个值是有意义的 - 那么它应该定义下面的特殊方法,使它像一个集合一样.

如果你想…所以,你写…Python调用…
序列的数量len(s)s.__len__()
否包含特定的值x in ss.__contains__(x)

字典(映射)

如果你想…所以,你写…Python调用…
通过它的key来获得值x[key]x.__getitem__(key)
通过它的key来设置一个值x[key] = valuex.__setitem__(key, value)
删除键值对del x[key]x.__delitem__(key)
为丢失的key提供默认值x[nonexistent_key]x.__missing__(nonexistent_key)

数字

如果你想…所以,你写…Python调用…
x + yx.__add__(y)
x - yx.__sub__(y)
x * yx.__mul__(y)
整除x / yx.__trueiv__(y)
x // yx.__floordiv__(v)
取余x % yx.__mod__(y)
整除与取余divmod(x, y)x.__divmod__(y)
平方x ** yx.__pow__(y)
左移x << yx.__lshift__(y)
右移x >> yx.__rshift__(y)
按位and运算x & yx.__and__(y)
按位xor或运算x ^ yx.__xor__(y)
按位or运算x | yx.__or__(y)

上述一组特殊方法采用第一种方法:给定x / y,它们提供了一种方法让x说"我知道如何用y整除自己".以下一组特殊方法解决了第二种方法:它们为y提供了一种方法来说"我知道如何成为分母,并将自己整除x".

如果你想…所以,你写…Python调用…
x + yx.__radd__(y)
x - yx.__rsub__(y)
x * yx.__rmul__(y)
整除x / yx.__rtrueiv__(y)
x // yx.__rfloordiv__(v)
取余x % yx.__rmod__(y)
整除与取余divmod(x, y)x.__rdivmod__(y)
平方x ** yx.__rpow__(y)
左移x << yx.__rlshift__(y)
右移x >> yx.__rrshift__(y)
按位and运算x & yx.__rand__(y)
按位xor或运算x ^ yx.__rxor__(y)
按位or运算x | yx.__ror__(y)

可是等等!还有更多!如果你正在进行"就地"操作,如x /= 3则可以定义更多特殊的方法.

如果你想…所以,你写…Python调用…
x + yx.__iadd__(y)
x - yx.__isub__(y)
x * yx.__imul__(y)
整除x / yx.__itrueiv__(y)
x // yx.__ifloordiv__(v)
取余x % yx.__imod__(y)
整除与取余divmod(x, y)x.__idivmod__(y)
平方x ** yx.__ipow__(y)
左移x << yx.__ilshift__(y)
右移x >> yx.__irshift__(y)
按位and运算x & yx.__iand__(y)
按位xor或运算x ^ yx.__ixor__(y)
按位or运算x | yx.__ior__(y)

还有一些"单个数"数学运算可以让你自己对类似数字的对象进行数学运算.

如果你想…所以,你写…Python调用…
负数-xx.__neg__()
正数+xx.__pos__()
绝对值abs(x)x.__abs__()
~xx.__invert__()
复数complex(x)x.__complex__()
整数int(x)x.__int__()
浮点数float(x)x.__float__()
四舍五入到最近的整数round(x)x.__round__()
四舍五入到最近的n位数round(x, n)x.__round__(n)
最小整数math.ceil(x)x.__ceil__()
最大整数math.floor(x)x.__floor__()
截断x到0的最接近的整数math.trunc(x)x.__trunc__()
数字作为列表索引a_list[x]a_list[x.__index__()]

比较

如果你想…所以,你写…Python调用…
等于x == yx.__eq__(y)
不等于x != yx.__ne__(y)
小于x < yx.__lt__(y)
小于等于x <= yx.__le__(y)
大于x > yx.__gt__(y)
大于等于x >= yx.__ge__(y)
布尔if x:x.__bool__()

序列化

如果你想…所以,你写…Python调用…
对象副本copy.copy(x)x.__copy__()
深拷贝copy.deepcopy(x)x.__deepcopy__()
序列化一个对象pickle.dump(x, file)x.__getstate__()
序列化一个对象pickle.dump(x, file)x.__reduce__()
序列化一个对象pickle.dump(x, file, protocol_version)x.__reduce_ex__(protocol_version)
取出恢复后的状态x = pickle.load(fp)x.__getnewargs__()
取出恢复后的状态x = pickle.load(fp)x.__setstate__()

with 语句

with块限定了运行时上下文;在执行with语句时,"进入"上下文,并在执行块中的最后一个语句后"退出"上下文.

如果你想…所以,你写…Python调用…
进入with语句块with x:x.__enter__()
退出with语句块with x:x.__exit__(exc_type, exc_value, traceback)

真正深奥的东西

如果你想…所以,你写…Python调用…
一个类的构造函数x = MyClass()x.__new__()
一个类的析构函数del xx.__del__()
只有一组特定的属性需要定义``x.__slots__()
hash码hash(x)x.__hash__()
获得一个属性的值x.colortype(x).__dict__['color'].__get__(x, type(x))
设置一个属性的值x.color = 'PapayaWhip'type(x).__dict__['color'].__set__(x, 'PapayaWhip')
删除一个属性del x.colortype(x).__dict__['color'].__del__(x)
一个对象是否是你的一个类的实例isinstance(x, MyClass)MyClass.__instancecheck__(x)
一个类是否是你的类的子类isinstance(C, MyClass)MyClass.__subclasscheck__(C)
一个类是否是抽象基类的实例isinstance(C, MyABC)MyABC.__subclasshook__(C)

函数和方法的区别

  • 都是用用来完成一个功能语句块,本质一样

  • 方法通过对象来调用.普通函数不需要

  • 方法定义第一个参数是self,函数不需要

实例对象的本质

a=()
a.方法()
等价
类.方法(a)
私有方法
双下划线开头的方法
只能在类的内部被调用的方法

@property装饰器

之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便,代码如下所示。

class Person(object):

    def __init__(self, name, age):
        self._name = name
        self._age = age

    # 访问器 - getter方法
    @property
    def name(self):
        return self._name

    # 访问器 - getter方法
    @property
    def age(self):
        return self._age

    # 修改器 - setter方法
    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 12)
    person.play()
    person.age = 22
    person.play()
    # person.name = '白元芳'  # AttributeError: can't set attribute


if __name__ == '__main__':
    main()

__slots__魔法

我们讲到这里,不知道大家是否已经意识到,Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用。

class Person(object):

    # 限定Person对象只能绑定_name, _age和_gender属性
    __slots__ = ('_name', '_age', '_gender')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = Person('王大锤', 22)
    person.play()
    person._gender = '男'
    # AttributeError: 'Person' object has no attribute '_is_gay'
    # person._is_gay = True

静态方法和类方法

​ 之前,我们在类中定义的方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们写在类中的方法并不需要都是对象方法,例如我们定义一个“三角形”类,通过传入三条边长来构造三角形,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构成三角形,这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),所以这个方法是属于三角形类而并不属于三角形对象的。我们可以使用静态方法来解决这类问题,代码如下所示。

from math import sqrt


class Triangle(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self._a + self._b + self._c

    def area(self):
        half = self.perimeter() / 2
        return sqrt(half * (half - self._a) *
                    (half - self._b) * (half - self._c))


def main():
    a, b, c = 3, 4, 5
    # 静态方法和类方法都是通过给类发消息来调用的
    if Triangle.is_valid(a, b, c):
        t = Triangle(a, b, c)
        print(t.perimeter())
        # 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
        # print(Triangle.perimeter(t))
        print(t.area())
        # print(Triangle.area(t))
    else:
        print('无法构成三角形.')


if __name__ == '__main__':
    main()

​ 和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示。

from time import time, localtime, sleep


class Clock(object):
    """数字时钟"""

    def __init__(self, hour=0, minute=0, second=0):
        self._hour = hour
        self._minute = minute
        self._second = second

    @classmethod
    def now(cls):
        ctime = localtime(time())
        return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)

    def run(self):
        """走字"""
        self._second += 1
        if self._second == 60:
            self._second = 0
            self._minute += 1
            if self._minute == 60:
                self._minute = 0
                self._hour += 1
                if self._hour == 24:
                    self._hour = 0

    def show(self):
        """显示时间"""
        return '%02d:%02d:%02d' % \
               (self._hour, self._minute, self._second)


def main():
    # 通过类方法创建对象并获取系统时间
    clock = Clock.now()
    while True:
        print(clock.show())
        sleep(1)
        clock.run()


if __name__ == '__main__':
    main()

类之间的关系

简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。

  • is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
  • has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
  • use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。

[外链图片转存失败(img-g2pbxlLr-1565166677221)(https://github.com/jackfrued/Python-100-Days/raw/master/Day01-15/res/uml-example.png)]

​ 利用类之间的这些关系,我们可以在已有类的基础上来完成某些操作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。复用现有的代码不仅可以减少开发的工作量,也有利于代码的管理和维护,这是我们在日常工作中都会使用到的技术手段。

继承和多态

​ 可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。

​ 提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。

​ 子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为。

super

​ 在子类的函数中,若是要重用父类中某个函数的功能,可以直接通过 super 来调用父类中的函数,当然也可以通过 类名.func() 来调用,只不过这样与调用普通函数无异。这个经常使用在需要对父类的同名方法进行扩展的场景~

class Father:
    def say(self):
        print('Hello !')

    def introduce(self):
        print('Father')

class Son(Father):
    def say(self):
        super().say()
        # Father.say(self)   # 通过 类名.func(),输出结果一致
        print('你好 ~')

p = Son()
p.say()

子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。

class Person(object):
    """人"""

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        print('%s正在愉快的玩耍.' % self._name)

    def watch_av(self):
        if self._age >= 18:
            print('%s正在观看爱情动作片.' % self._name)
        else:
            print('%s只能观看《熊出没》.' % self._name)


class Student(Person):
    """学生"""

    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self._grade = grade

    @property
    def grade(self):
        return self._grade

    @grade.setter
    def grade(self, grade):
        self._grade = grade

    def study(self, course):
        print('%s的%s正在学习%s.' % (self._grade, self._name, course))


class Teacher(Person):
    """老师"""

    def __init__(self, name, age, title):
        super().__init__(name, age)
        self._title = title

    @property
    def title(self):
        return self._title

    @title.setter
    def title(self, title):
        self._title = title

    def teach(self, course):
        print('%s%s正在讲%s.' % (self._name, self._title, course))


def main():
    stu = Student('王大锤', 15, '初三')
    stu.study('数学')
    stu.watch_av()
    t = Teacher('小明', 38, '砖家')
    t.teach('Python程序设计')
    t.watch_av()


if __name__ == '__main__':
    main()

​ 通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。

from abc import ABCMeta, abstractmethod


class Pet(object, metaclass=ABCMeta):
    """宠物"""

    def __init__(self, nickname):
        self._nickname = nickname
        
    # 抽象方法,必须要被重写
    @abstractmethod
    def make_voice(self):
        """发出声音"""
        pass


class Dog(Pet):
    """狗"""

    def make_voice(self):
        print('%s: 汪汪汪...' % self._nickname)


class Cat(Pet):
    """猫"""

    def make_voice(self):
        print('%s: 喵...喵...' % self._nickname)


def main():
    pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
    for pet in pets:
        pet.make_voice()


if __name__ == '__main__':
    main()

​ 在上面的代码中,我们将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过abc模块的ABCMeta元类和abstractmethod包装器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,DogCat两个子类分别对Pet类中的make_voice抽象方法进行了重写并给出了不同的实现版本,当我们在main函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。

综合案例

案例1:奥特曼打小怪兽
from abc import ABCMeta, abstractmethod
from random import randint, randrange


class Fighter(object, metaclass=ABCMeta):
    """战斗者"""

    # 通过__slots__魔法限定对象可以绑定的成员变量
    __slots__ = ('_name', '_hp')

    def __init__(self, name, hp):
        """初始化方法

        :param name: 名字
        :param hp: 生命值
        """
        self._name = name
        self._hp = hp

    @property
    def name(self):
        return self._name

    @property
    def hp(self):
        return self._hp

    @hp.setter
    def hp(self, hp):
        self._hp = hp if hp >= 0 else 0

    @property
    def alive(self):
        return self._hp > 0

    @abstractmethod
    def attack(self, other):
        """攻击

        :param other: 被攻击的对象
        """
        pass


class Ultraman(Fighter):
    """奥特曼"""

    __slots__ = ('_name', '_hp', '_mp')

    def __init__(self, name, hp, mp):
        """初始化方法

        :param name: 名字
        :param hp: 生命值
        :param mp: 魔法值
        """
        super().__init__(name, hp)
        self._mp = mp

    def attack(self, other):
        other.hp -= randint(15, 25)

    def huge_attack(self, other):
        """究极必杀技(打掉对方至少50点或四分之三的血)
        :param other: 被攻击的对象
        :return: 使用成功返回True否则返回False
        """
        if self._mp >= 50:
            self._mp -= 50
            injury = other.hp * 3 // 4
            injury = injury if injury >= 50 else 50
            other.hp -= injury
            return True
        else:
            self.attack(other)
            return False

    def magic_attack(self, others):
        """魔法攻击
        :param others: 被攻击的群体
        :return: 使用魔法成功返回True否则返回False
        """
        if self._mp >= 20:
            self._mp -= 20
            for temp in others:
                if temp.alive:
                    temp.hp -= randint(10, 15)
            return True
        else:
            return False

    def resume(self):
        """恢复魔法值"""
        incr_point = randint(1, 10)
        self._mp += incr_point
        return incr_point

    def __str__(self):
        return '~~~%s奥特曼~~~\n' % self._name + \
            '生命值: %d\n' % self._hp + \
            '魔法值: %d\n' % self._mp


class Monster(Fighter):
    """小怪兽"""

    __slots__ = ('_name', '_hp')

    def attack(self, other):
        other.hp -= randint(10, 20)

    def __str__(self):
        return '~~~%s小怪兽~~~\n' % self._name + \
            '生命值: %d\n' % self._hp


def is_any_alive(monsters):
    """判断有没有小怪兽是活着的"""
    for monster in monsters:
        if monster.alive > 0:
            return True
    return False


def select_alive_one(monsters):
    """选中一只活着的小怪兽"""
    monsters_len = len(monsters)
    while True:
        index = randrange(monsters_len)
        monster = monsters[index]
        if monster.alive > 0:
            return monster


def display_info(ultraman, monsters):
    """显示奥特曼和小怪兽的信息"""
    print(ultraman)
    for monster in monsters:
        print(monster, end='')


def main():
    u = Ultraman('迪迦', 1000, 120)
    m1 = Monster('炎魔战士', 250)
    m2 = Monster('哥布纽', 500)
    m3 = Monster('迪莫杰厄', 750)
    ms = [m1, m2, m3]
    fight_round = 1
    while u.alive and is_any_alive(ms):
        print('========第%02d回合========' % fight_round)
        m = select_alive_one(ms)  # 选中一只小怪兽
        skill = randint(1, 10)   # 通过随机数选择使用哪种技能
        if skill <= 6:  # 60%的概率使用普通攻击
            print('%s使用普通攻击打了%s.' % (u.name, m.name))
            u.attack(m)
            print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
        elif skill <= 9:  # 30%的概率使用魔法攻击(可能因魔法值不足而失败)
            if u.magic_attack(ms):
                print('%s使用了魔法攻击.' % u.name)
            else:
                print('%s使用魔法失败.' % u.name)
        else:  # 10%的概率使用究极必杀技(如果魔法值不足则使用普通攻击)
            if u.huge_attack(m):
                print('%s使用究极必杀技虐了%s.' % (u.name, m.name))
            else:
                print('%s使用普通攻击打了%s.' % (u.name, m.name))
                print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
        if m.alive > 0:  # 如果选中的小怪兽没有死就回击奥特曼
            print('%s回击了%s.' % (m.name, u.name))
            m.attack(u)
        display_info(u, ms)  # 每个回合结束后显示奥特曼和小怪兽的信息
        fight_round += 1
    print('\n========战斗结束!========\n')
    if u.alive > 0:
        print('%s奥特曼胜利!' % u.name)
    else:
        print('小怪兽胜利!')


if __name__ == '__main__':
    main()
案例2:扑克游戏
import random


class Card(object):
    """一张牌"""

    def __init__(self, suite, face):
        self._suite = suite
        self._face = face

    @property
    def face(self):
        return self._face

    @property
    def suite(self):
        return self._suite

    def __str__(self):
        if self._face == 1:
            face_str = 'A'
        elif self._face == 11:
            face_str = 'J'
        elif self._face == 12:
            face_str = 'Q'
        elif self._face == 13:
            face_str = 'K'
        else:
            face_str = str(self._face)
        return '%s%s' % (self._suite, face_str)
    
    def __repr__(self):
        return self.__str__()


class Poker(object):
    """一副牌"""

    def __init__(self):
        self._cards = [Card(suite, face) 
                       for suite in '♠♥♣♦'
                       for face in range(1, 14)]
        self._current = 0

    @property
    def cards(self):
        return self._cards

    def shuffle(self):
        """洗牌(随机乱序)"""
        self._current = 0
        random.shuffle(self._cards)

    @property
    def next(self):
        """发牌"""
        card = self._cards[self._current]
        self._current += 1
        return card

    @property
    def has_next(self):
        """还有没有牌"""
        return self._current < len(self._cards)


class Player(object):
    """玩家"""

    def __init__(self, name):
        self._name = name
        self._cards_on_hand = []

    @property
    def name(self):
        return self._name

    @property
    def cards_on_hand(self):
        return self._cards_on_hand

    def get(self, card):
        """摸牌"""
        self._cards_on_hand.append(card)

    def arrange(self, card_key):
        """玩家整理手上的牌"""
        self._cards_on_hand.sort(key=card_key)


# 排序规则-先根据花色再根据点数排序
def get_key(card):
    return (card.suite, card.face)


def main():
    p = Poker()
    p.shuffle()
    players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
    for _ in range(13):
        for player in players:
            player.get(p.next)
    for player in players:
        print(player.name + ':', end=' ')
        player.arrange(get_key)
        print(player.cards_on_hand)


if __name__ == '__main__':
    main()

说明: 大家可以自己尝试在上面代码的基础上写一个简单的扑克游戏,例如21点(Black Jack),游戏的规则可以自己在网上找一找。

案例3:工资结算系统
"""
某公司有三种类型的员工 分别是部门经理、程序员和销售员
需要设计一个工资结算系统 根据提供的员工信息来计算月薪
部门经理的月薪是每月固定15000元
程序员的月薪按本月工作时间计算 每小时150元
销售员的月薪是1200元的底薪加上销售额5%的提成
"""
from abc import ABCMeta, abstractmethod


class Employee(object, metaclass=ABCMeta):
    """员工"""

    def __init__(self, name):
        """
        初始化方法

        :param name: 姓名
        """
        self._name = name

    @property
    def name(self):
        return self._name

    @abstractmethod
    def get_salary(self):
        """
        获得月薪

        :return: 月薪
        """
        pass


class Manager(Employee):
    """部门经理"""

    def get_salary(self):
        return 15000.0


class Programmer(Employee):
    """程序员"""

    def __init__(self, name, working_hour=0):
        super().__init__(name)
        self._working_hour = working_hour

    @property
    def working_hour(self):
        return self._working_hour

    @working_hour.setter
    def working_hour(self, working_hour):
        self._working_hour = working_hour if working_hour > 0 else 0

    def get_salary(self):
        return 150.0 * self._working_hour


class Salesman(Employee):
    """销售员"""

    def __init__(self, name, sales=0):
        super().__init__(name)
        self._sales = sales

    @property
    def sales(self):
        return self._sales

    @sales.setter
    def sales(self, sales):
        self._sales = sales if sales > 0 else 0

    def get_salary(self):
        return 1200.0 + self._sales * 0.05


def main():
    emps = [
        Manager('刘备'), Programmer('诸葛亮'),
        Manager('曹操'), Salesman('荀彧'),
        Salesman('吕布'), Programmer('张辽'),
        Programmer('赵云')
    ]
    for emp in emps:
        if isinstance(emp, Programmer):
            emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
        elif isinstance(emp, Salesman):
            emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
        # 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
        print('%s本月工资为: ¥%s元' %
              (emp.name, emp.get_salary()))


if __name__ == '__main__':
    main()

反射

反射自省的概念

自省:自省就是能够获得自身的结构和方法,给开发者可以灵活的调用,给定一个对象,返回该对象的所有属性和函数列表,或给定对象和该对象的函数或者属性的名字,返回对象的函数或者属性实例。
反射就是通过字字符串的形式来操作对象或者模块的成员,一种基于字符串的事件驱动。

hasattr

判断对象中是否有这个方法或变量

class Person(object):
    def __init__(self,name):
        self.name = name
    def talk(self):
        print("%s正在交谈"%self.name)

p = Person("laowang")        
print(hasattr(p,"talk"))    # True。因为存在talk方法
print(hasattr(p,"name"))    # True。因为存在name变量
print(hasattr(p,"abc"))     # False。因为不存在abc方法或变量

getattr

获取对象中的方法或变量的内存地址

class Person(object):
    def __init__(self,name):
        self.name = name
    def talk(self):
        print("%s正在交谈"%self.name)
p = Person("laowang")

n = getattr(p,"name")   # 获取name变量的内存地址
print(n)                # 此时打印的是:laowang

f = getattr(p,"talk")   # 获取talk方法的内存地址
f()                     # 调用talk方法

我们发现getattr有三个参数,那么第三个参数是做什么用的呢?
s = getattr(p,"abc","not find")
print(s)                # 打印结果:not find。因为abc在对象p中找不到,本应该报错,属性找不到,但因为修改了找不到就输出not find

setattr

为对象添加变量或方法

def abc(self):
    print("%s正在交谈"%self.name)

class Person(object):
    def __init__(self,name):
        self.name = name

p = Person("laowang")
setattr(p,"talk",abc)   # 将abc函数添加到对象中p中,并命名为talk
p.talk(p)               # 调用talk方法,因为这是额外添加的方法,需手动传入对象


setattr(p,"age",30)     # 添加一个变量age,复制为30
print(p.age)            # 打印结果:30

delattr

删除对象中的变量。注意:不能用于删除方法

class Person(object):
    def __init__(self,name):
        self.name = name
    def talk(self):
        print("%s正在交谈"%self.name)

p = Person("laowang")

delattr(p,"name")       # 删除name变量
print(p.name)           # 此时将报错

Metaclass元类

0 元类type

元类是python高阶语法. 合理的使用可以减少大量重复性的代码.

元类实际上做了以下三方面的工作:

  • 干涉创建类的过程
  • 修改类
  • 返回修改之后的类
为什么使用元类?

为什么要使用元类这种模糊且容易出错的功能?
一般情况下,我们并不会使用元类,99%的开发者并不会用到元类,所以一般不用考虑这个问题。
元类主用用于创建API,一个典型的例子就是Django的ORM。
它让我们可以这样定义一个类:

class Person(models.Model):
  name = models.CharField(max_length=30)
  age = models.IntegerField()
    
guy = Person(name='bob', age='35')
print(guy.age)

返回的结果是int类型而不是IntegerField对象。这是因为models.Model使用了元类,它会将Python中定义的字段转换成数据库中的字段。
通过使用元类,Django将复杂的接口转换成简单的接口。

原型:type(类名,基类元组(可以为空,用于继承), 包含属性或函数的字典)

以下两种写法都可以:

type(‘Class’,(object,),dict(hello=fun()))

type(‘Class’,(object,),{“hello”:fun()})

1、class 自定义的类名称

2、(object,)是继承类,的元组,如果只有一个就写这种形势(object,);多个(object,xxxx,)

3、dict(hello=fun()) 或 {“hello”:fun()} 第三个参数,是一个字典等号左是 自定义的方法名,右侧是已写好的方法名,这个要注意,有参数且没有默认值的情况下,要加括号;

def fun():
    print('hello world!')


if __name__=="__main__":
    Hello = type('Hello',(object,),dict(hello=fun()))
    tc = Hello()
    tc.hello

引用:

h 相当于接收Hello类;tc = h()实例化类;tc.hello方法,调用的其实是我们定义的fun方法。

Hello = type('Hello',(object,),dict(f1=fun)
tc = Hello()
tc.f1

type()动态创建类后,还可以添加更多的方法和属性:

def add(x,y):
  	return x + y

Hello.add = add(1, 2)

调用:

print(tc.add)

1 自定义元类

​ 元类是用来创建其他的类,它的实例就是其他的类。
执行类定义时,解释器会先寻找类属性__metaclass__,如果此属性中未定义,则向上查找其父类中的__metaclass__
type__init__函数有三个位置参数,分别为:类名称,父类名称元祖,类属性字典;可直接使用type()快速创建类对象:
People = type('People',(object,),dict(show = fn, setage = fn1, age = None))
也可子类化type类后,创建元类,再创建类对象:

from time import ctime

class MetaC(type):
    def __init__(self, *args):
        super().__init__(*args)
        print('调用类的init')
    
    def __call__(self, *args, **kwargs):
        print('调用类的call')
        _instance = super().__call__(*args, **kwargs)
        print('call return %s' %_instance)
        return _instance

class Foo(metaclass=MetaC):
    # __metaclass__ = MetaC

    def __init__(self, version=None):
        print('调用对象的init')

    def __new__(cls, *args, **kwargs):
        print('调用对象的new')
        _instance = super().__new__(cls)
        print('new return %s' %_instance)
        return _instance
    
foo = Foo('hello')

运行结果:

>>
调用类的init
调用类的call
调用对象的new
new return <__main__.Foo object at 0x0000018D2F57EF98>
调用对象的init
call return <__main__.Foo object at 0x0000018D2F57EF98>

2 单例模式的实现

下面是几种单例模式的实现方法,有些会用到上述的元类知识。

2.1 装饰器实现单例
import weakref
frome functools import wraps

def single_obj(cls):
	#实例缓存为弱引用,实例不被使用时会释放该实例
    _spam_cache = weakref.WeakValueDictionary()
    @wraps(cls)
    def wrapper(*args, **kwargs):
        if cls not in _spam_cache :
            _instance = cls(*args, **kwargs)
            _spam_cache [cls] = _instance
            return _instance
        else:
            return _spam_cache [cls]
    return wrapper

@single_obj
class A():
    def __init__(self, version):
        self.version = version
        print(self.version)

a1 = A(1.3)
a2 = A(1.2)
print('a1 id:',id(a1))
print('a2 id:',id(a2))
2.2 利用元类,类的__call__方法实现单例
class Singleton(type):
    def __init__(self, *args, **kwargs):
        self._instance = None
        super().__init__(*args, **kwargs)
    
    def __call__(self, *args, **kwargs):
        if self._instance is None:
            self._instance = super().__call__(*args, **kwargs)
            return self._instance
        else:
            return self._instance

class B(metaclass=Singleton):
    def __init__(self, name):
        self.name = name
        print(name)

b1 = B('what')
b2 = B('hell')
print(b2.name)
print('b1 id:',id(b1))
print('b2 id:',id(b2))
2.3 通过__new__实现单例(此方法不可行)
class C(object):
    _instance = None

    def __init__(self, name):
        self.name = name
        print(name)

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

c1 = C('dsfsfd')
c2 = C('java')

print('c1 id:',id(c1))
print('c2 id:',id(c2))
  • 若采用上述方法,无论类是否实例化,__init__都会被调用,实例对象会被修改;

异常类型

1.1 Python内置异常
Python的异常处理能力是很强大的,它有很多内置异常,可向用户准确反馈出错信息。

内置异常类的层次结构如下:

BaseException # 所有异常的基类
±- SystemExit # 解释器请求退出
±- KeyboardInterrupt # 用户中断执行(通常是输入^C)
±- GeneratorExit # 生成器(generator)发生异常来通知退出
±- Exception # 常规异常的基类
±- StopIteration # 迭代器没有更多的值
±- StopAsyncIteration # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代
±- ArithmeticError # 各种算术错误引发的内置异常的基类
| ±- FloatingPointError # 浮点计算错误
| ±- OverflowError # 数值运算结果太大无法表示
| ±- ZeroDivisionError # 除(或取模)零 (所有数据类型)
±- AssertionError # 当assert语句失败时引发
±- AttributeError # 属性引用或赋值失败
±- BufferError # 无法执行与缓冲区相关的操作时引发
±- EOFError # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发
±- ImportError # 导入模块/对象失败
| ±- ModuleNotFoundError # 无法找到模块或在在sys.modules中找到None
±- LookupError # 映射或序列上使用的键或索引无效时引发的异常的基类
| ±- IndexError # 序列中没有此索引(index)
| ±- KeyError # 映射中没有这个键
±- MemoryError # 内存溢出错误(对于Python 解释器不是致命的)
±- NameError # 未声明/初始化对象 (没有属性)
| ±- UnboundLocalError # 访问未初始化的本地变量
±- OSError # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类
| ±- BlockingIOError # 操作将阻塞对象(e.g. socket)设置为非阻塞操作
| ±- ChildProcessError # 在子进程上的操作失败
| ±- ConnectionError # 与连接相关的异常的基类
| | ±- BrokenPipeError # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入
| | ±- ConnectionAbortedError # 连接尝试被对等方中止
| | ±- ConnectionRefusedError # 连接尝试被对等方拒绝
| | ±- ConnectionResetError # 连接由对等方重置
| ±- FileExistsError # 创建已存在的文件或目录
| ±- FileNotFoundError # 请求不存在的文件或目录
| ±- InterruptedError # 系统调用被输入信号中断
| ±- IsADirectoryError # 在目录上请求文件操作(例如 os.remove())
| ±- NotADirectoryError # 在不是目录的事物上请求目录操作(例如 os.listdir())
| ±- PermissionError # 尝试在没有足够访问权限的情况下运行操作
| ±- ProcessLookupError # 给定进程不存在
| ±- TimeoutError # 系统函数在系统级别超时
±- ReferenceError # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象
±- RuntimeError # 在检测到不属于任何其他类别的错误时触发
| ±- NotImplementedError # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现
| ±- RecursionError # 解释器检测到超出最大递归深度
±- SyntaxError # Python 语法错误
| ±- IndentationError # 缩进错误
| ±- TabError # Tab和空格混用
±- SystemError # 解释器发现内部错误
±- TypeError # 操作或函数应用于不适当类型的对象
±- ValueError # 操作或函数接收到具有正确类型但值不合适的参数
| ±- UnicodeError # 发生与Unicode相关的编码或解码错误
| ±- UnicodeDecodeError # Unicode解码错误
| ±- UnicodeEncodeError # Unicode编码错误
| ±- UnicodeTranslateError # Unicode转码错误
±- Warning # 警告的基类
±- DeprecationWarning # 有关已弃用功能的警告的基类
±- PendingDeprecationWarning # 有关不推荐使用功能的警告的基类
±- RuntimeWarning # 有关可疑的运行时行为的警告的基类
±- SyntaxWarning # 关于可疑语法警告的基类
±- UserWarning # 用户代码生成警告的基类
±- FutureWarning # 有关已弃用功能的警告的基类
±- ImportWarning # 关于模块导入时可能出错的警告的基类
±- UnicodeWarning # 与Unicode相关的警告的基类
±- BytesWarning # 与bytes和bytearray相关的警告的基类
±- ResourceWarning # 与资源使用相关的警告的基类。被默认警告过滤器忽略。

详细说明请参考:https://docs.python.org/3/library/exceptions.html#base-classes

异常捕获

当发生异常时,我们就需要对异常进行捕获,然后进行相应的处理。

python的异常捕获常用try…except…结构

  • 把可能发生错误的语句放在try模块里

  • 用except来处理异常

  • 每一个try,都必须至少对应一个except。

  • 此外,与python异常相关的关键字主要有:

    • try/except 捕获异常并处理

    • pass 忽略异常

    • as 定义异常实例(except MyError as e)

    • else 如果try中的语句没有引发异常,则执行else中的语句

    • finally 无论是否出现异常,都执行的代码

    • raise 抛出/引发异常

捕获所有异常

包括键盘中断和程序退出请求(用sys.exit()就无法退出程序了,因为异常被捕获了),因此慎用。

try:  
    <语句>
except:
  print('异常说明')
2.2 捕获指定异常
try:
     <语句>
except <异常名>:
	print('异常说明')

万能异常:

try:
     <语句>
except Exception:
  print('异常说明')

一个例子:

try:
    f = open("file-not-exists", "r")
except IOError as e:
	print("open exception: %s: %s" %(e.errno, e.strerror))
2.3 捕获多个异常

捕获多个异常有两种方式,第一种是一个except同时处理多个异常,不区分优先级:

try:
   <语句>
except (<异常名1>, <异常名2>, ...):
  print('异常说明')

第二种是区分优先级的:

try:
     <语句>
except <异常名1>:
  print('异常说明1')
except <异常名2>:
  print('异常说明2')
except <异常名3>:
  print('异常说明3')

该种异常处理语法的规则是:

执行try下的语句,如果引发异常,则执行过程会跳到第一个except语句。
如果第一个except中定义的异常与引发的异常匹配,则执行该except中的语句。
如果引发的异常不匹配第一个except,则会搜索第二个except,允许编写的except数量没有限制。
如果所有的except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中。

2.4 异常中的else

如果判断完没有某些异常之后还想做其他事,就可以使用下面这样的else语句。

try:
     <语句>
except <异常名1>:
 print('异常说明1')
except <异常名2>:
 print('异常说明2')
else:
 <语句>  # try语句中没有异常则执行此段代码
2.5 异常中的finally
try...finally...语句无论是否发生异常都将会执行最后的代码。

try:
     <语句>

finally:
	<语句>

看一个示例:

str1 = 'hello world'
try:
    int(str1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)
else:
    print('try内没有异常')
finally:
    print('无论异常与否,都会执行我')
2.6 raise主动触发异常

可以使用raise语句自己触发异常,raise语法格式如下:

raise [Exception [, args [, traceback]]]

语句中Exception是异常的类型(例如ValueError),参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是"None"。最后一个参数是跟踪异常对象,也是可选的(在实践中很少使用)。

看一个例子:

def not_zero(num):
    try:
        if num == 0:
            raise ValueError('参数错误')
        return num
    except Exception as e:
        print(e)

not_zero(0)

实现迭代器

​ Python循环语句可以作用域任何序列类型,包括列表、元组以及字符串。

​ 实际上for循环能够作用于任何可迭代的对象,除了for语句,python中所有会从左至右的迭代工具都是如此,这些迭代工具包括:for循环、列表解析、in成员关系测试以及map内置函数等…
​ 这里就涉及到很重要的一个概念-----可迭代对象,除此之外还有一个与它很类似的概念,叫做迭代对象,很多人经常分不清楚他们。

​ 迭代对象是指实现了__iter____next__方法的对象,而可迭代对象可以只实现__iter__方法,也可以两个都实现。有的可迭代对象的迭代对象就是它本身。说了那么多,不如我们直接自己实现一下:

class MyRange(object):
    def __init__(self, n):
        self.idx = 0
        self.n = n
        
    def __iter__(self):
        return self

    def __next__(self):
        if self.idx < self.n:
            val = self.idx
            self.idx += 1
            return val
        else:
            raise StopIteration()
            
myRange = MyRange(3)
for i in myRange:
    print i 

句。
如果引发的异常不匹配第一个except,则会搜索第二个except,允许编写的except数量没有限制。
如果所有的except都不匹配,则异常会传递到下一个调用本代码的最高层try代码中。

2.4 异常中的else

如果判断完没有某些异常之后还想做其他事,就可以使用下面这样的else语句。

try:
     <语句>
except <异常名1>:
 print('异常说明1')
except <异常名2>:
 print('异常说明2')
else:
 <语句>  # try语句中没有异常则执行此段代码
2.5 异常中的finally
try...finally...语句无论是否发生异常都将会执行最后的代码。

try:
     <语句>

finally:
	<语句>

看一个示例:

str1 = 'hello world'
try:
    int(str1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)
else:
    print('try内没有异常')
finally:
    print('无论异常与否,都会执行我')
2.6 raise主动触发异常

可以使用raise语句自己触发异常,raise语法格式如下:

raise [Exception [, args [, traceback]]]

语句中Exception是异常的类型(例如ValueError),参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是"None"。最后一个参数是跟踪异常对象,也是可选的(在实践中很少使用)。

看一个例子:

def not_zero(num):
    try:
        if num == 0:
            raise ValueError('参数错误')
        return num
    except Exception as e:
        print(e)

not_zero(0)

实现迭代器

​ Python循环语句可以作用域任何序列类型,包括列表、元组以及字符串。

​ 实际上for循环能够作用于任何可迭代的对象,除了for语句,python中所有会从左至右的迭代工具都是如此,这些迭代工具包括:for循环、列表解析、in成员关系测试以及map内置函数等…
​ 这里就涉及到很重要的一个概念-----可迭代对象,除此之外还有一个与它很类似的概念,叫做迭代对象,很多人经常分不清楚他们。

​ 迭代对象是指实现了__iter____next__方法的对象,而可迭代对象可以只实现__iter__方法,也可以两个都实现。有的可迭代对象的迭代对象就是它本身。说了那么多,不如我们直接自己实现一下:

class MyRange(object):
    def __init__(self, n):
        self.idx = 0
        self.n = n
        
    def __iter__(self):
        return self

    def __next__(self):
        if self.idx < self.n:
            val = self.idx
            self.idx += 1
            return val
        else:
            raise StopIteration()
            
myRange = MyRange(3)
for i in myRange:
    print i 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值