面向对象

编程范式

对不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式
分为两类:面向过程编程和面向对象编程

面向过程编程(Procedural Programming)

面向过程:过程指的是解决问题的步骤,机械式的思维方式
优点:复杂的问题流程化,进而简单化
缺点:可扩展性差,如果添加功能需要修改一整个流水线

面向对象编程(Object Oriented Programing)

面向对象:核心就是对象,对象是特征与技能的结合体,站在上帝的视角来模拟这个世界
优点:可扩展性强
缺点:编程复杂度高
应用场景:用户需求经常变化,互联网应用,游戏(改游戏参数),企业内部应用

三大特性:
  • Encapsulation 封装
    在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法
  • Inheritance 继承
    一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
  • Polymorphism 多态
    多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。

面向对象介绍

1.类

类就是一系列对象相似的特征与技能的结合体
强调:站在不同的角度,得到的分类是不一样的
在现实世界中:先有对象,后总结出来类
在程序中:先定义类,后调用类来产生对象;类似开公司招聘,会先分好各部门招聘人才的特征需求(类),再进行对人招聘(对象)
举例:
在现实世界中:

  • 对象1:王二丫
    特征:
    学校=luffycity
    姓名=王二丫
    性别=女
    年龄=18
    技能:吃饭,睡觉,学习
  • 对象2:李三炮
    特征:
    学校=luffycity
    姓名=李三炮
    性别=男
    年龄=38
    技能:吃饭,睡觉,学习
  • 对象3:张铁蛋
    特征:
    学校=luffycity
    姓名=张铁蛋
    性别=男
    年龄=48
    技能:吃饭,睡觉,学习
    总结现实中luffycity的学生类:
    相似的特征:学校=luffycity
    相似的技能:吃饭,睡觉,学习
    在程序中:
# 先定义类
class Luffycity:    #类的名
    school = "luffycity"    #用变量表示特征——类的数据属性
    def learn(self):    #用函数表示技能——类的函数属性
        print("is learing")
    def eat(self):
        print("is eating")
    def sleep(self):
        print("is sleeping")
# 后产生对象
stu1 = Luffycity()  #实例化出对象
stu2 = Luffycity()
stu3 = Luffycity()
print(stu1)
print(stu2)
print(stu3)
输出
<__main__.Luffycity object at 0x02BEF0D0>
<__main__.Luffycity object at 0x02BEF100>
<__main__.Luffycity object at 0x02BEF2C8>

2.如何使用类

不同于函数,类在class定义类的时候就会执行类体(类的内部代码)

  • 查看类的名称空间
print(Luffycity.__dict__)
{'__module__': '__main__', 'school': 'luffycity', 'learn': <function Luffycity.learn at 0x0320E850>, 'eat': <function Luffycity.eat at 0x0320E808>, 'sleep': <function Luffycity.sleep at 0x0320E7C0>, '__dict__': <attribute '__dict__' of 'Luffycity' objects>, '__weakref__': <attribute '__weakref__' of 'Luffycity' objects>, '__doc__': None}
  • 查看类的内部变量(数据属性)和函数(函数属性)
print(Luffycity.__dict__["school"])
print(Luffycity.__dict__["learn"])
输出
luffycity
<function Luffycity.learn at 0x0143E850>	#函数内存地址

可以类似import模块用类名加点
Luffycity.school等同于Luffycity._ dict _[“school”]

  • 增加类的内部变量

Luffycity.country = “China”  #将新增的变量名country作为key,China作为value传入类的名称空间

  • 删除类的内部变量

del Luffycity.country

  • 修改类的内部变量

Luffycity.school = “”Luffycity”

3.如何使用对象

_ init _方法用来为对象定制对象自己独有的特征,注意——init——函数在类的调用或者实例化时会进行执行,可以有任意代码,但一定不能有返回值

class Luffycity:    #类的名
    school = "luffycity"    #用变量表示特征
    def __init__(self,name,sex,age):
        self.Name = name	#在self对象的名称空间里增加数据属性,可以用__dict__查看
        self.Sex = sex
        self.Age = age
    def learn(self):    #用函数表示技能
        print("is learing")
    def eat(self):
        print("is eating")
    def sleep(self):
        print("is sleeping")
stu1 = Luffycity("王二丫","女",18)
  • 加上——init——方法后实例化的步骤:
  1. 先产生一个空对象stu1
  2. 再调用——init——函数Luffycity. _init _(self)为对象增加独有属性,self就是刚刚产生的对象
  3. 其实init函数就是将独有属性加入到对象的名称空间里
  • 查询对象属性
    stu1.Name
  • 增加对象属性
    stu1.Class = “A班”
  • 修改对象属性
    stu1.Name = “杨飞”
  • 删除对象属性
    del stu1.Name

4.属性查找与绑定方法

  • 类中的数据属性:是所有对象共有的
print(Luffycity.school,id(Luffycity.school))
print(stu1.school,id(stu1.school))
print(stu2.school,id(stu2.school))
print(stu3.school,id(stu3.school))
输出
luffycity 12394032
luffycity 12394032
luffycity 12394032
luffycity 12394032	#可以看到所有对象的数据属性ID与类的是一样的
  • 类中的函数属性

是绑定给对象使用的,绑定到不同对象是不同的绑定方法

print(Luffycity.learn,id(Luffycity.learn))
print(stu1.learn,id(stu1.learn))
print(stu2.learn,id(stu2.learn))
print(stu3.learn,id(stu3.learn))
输出
<function Luffycity.learn at 0x035FE850> 56617040
<bound method Luffycity.learn of <__main__.Luffycity object at 0x035FF0D0>> 26923272
<bound method Luffycity.learn of <__main__.Luffycity object at 0x035FF3D0>> 26923272
<bound method Luffycity.learn of <__main__.Luffycity object at 0x0360C598>> 26923272

对象调用绑定方法时,会把对象本身当做第一个参数传给self

class Luffycity:    #类的名
    school = "luffycity"    #用变量表示特征
    def __init__(self,name,sex,age):
        self.Name = name
        self.Sex = sex
        self.Age = age
    def learn(self,x):    #用函数表示技能
        print("%s is learing %s"% (self.Name,x))
stu1 = Luffycity("王二丫","女",18)

Luffycity.learn(stu1)
stu1.learn(hard)
输出
王二丫 is learing
王二丫 is learing hard

Luffycity.learn(stu1)是直接类调用函数属性,需要将一个生成的对象作为参数传进去,因为learn(self)函数要求传一个参数
stu1.learn(hard)是通过对象调用绑定的函数属性,他会将对象本身作为第一个参数传给self,然后“hard”作为参数传给x

  • 对象在查找属性时,在名称空间查找的顺序是:先在对象内查找,再去类中查找,再去父类中查找,如果查找不到报错,并不会去全局变量中查找
stu1.x = "x from stu1"
Luffycity.x = "x from Luffycity类"
print(stu1.x)
输出
x from stu1

5.补充说明

  • 站的角度不同,定义出的类是截然不同的;
  • 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类等;
  • 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类。

Python中一切皆对象,在python3中统一了类与类型的概念

如:l1 = [1,2,3],list其实就是属于类,赋值其实就是list类实例化对象的过程
 l1.append(4)其实就是调用了list类的函数属性,将l1作为self传给函数参数,本质与list.append(l1,4)是一个效果

总结面向对象的优点——可扩展性高

定义类并产生三个对象

class Chinese:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
        
p1=Chinese('egon',18,'male')
p2=Chinese('alex',38,'female')
p3=Chinese('wpq',48,'female')
如果我们新增一个类属性,将会立刻反映给所有对象,而对象却无需修改

class Chinese:
    country='China'
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex
    def tell_info(self):
        info='''
        国籍:%s
        姓名:%s
        年龄:%s
        性别:%s
        ''' %(self.country,self.name,self.age,self.sex)
        print(info)

p1=Chinese('egon',18,'male')
p2=Chinese('alex',38,'female')
p3=Chinese('wpq',48,'female')

print(p1.country)
p1.tell_info()

继承与派生

什么是继承

继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可以称为基类或超类,新建的类称为派生类或子类

python中类的继承分为:单继承和多继承

class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
    pass

class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
    pass

查看继承

>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类

  • 提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
继承与抽象(先抽象再继承)

抽象即抽取类似或者说比较像的部分。

抽象分成两个层次:

1.将奥巴马和梅西这俩对象比较像的部分抽取成类;

2.将人,猪,狗这三个类比较像的部分抽取成父类。

抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

在这里插入图片描述

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
在这里插入图片描述

记住的两句话:
  • 子类继承父类后,会继承父类的所有属性(数据属性和函数属性),可以将具有相同代码的子类将重复部分写进父类中,减少代码重复的作用
  • 在属性调用过程中,会先从对象本身查找,再去子类中查找,再去父类查找,最终查找不到就会报错(不会超出类去查找全局变量

派生

  • 当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Riven(Hero):
    camp='Noxus'
    def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
        print('from riven')
    def fly(self): #在自己这里定义新的
        print('%s is flying' %self.nickname)
  • 在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
class Riven(Hero):
    camp='Noxus'
    def __init__(self,nickname,aggressivity,life_value,skin):
        Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能也必须要传入参数
        self.skin=skin #新属性
    def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
        Hero.attack(self,enemy) #调用功能
        print('from riven')
    def fly(self): #在自己这里定义新的
        print('%s is flying' %self.nickname)

r1=Riven('锐雯雯',57,200,'比基尼')
r1.fly()
print(r1.skin)

'''
运行结果
锐雯雯 is flying
比基尼

继承的实现原理

python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个**MRO列表**就是一个简单的所有基类的线性顺序列表,例如:
F.mro() #等同于F. _ mro _
[<class ‘main.F’>, <class ‘main.D’>, <class ‘main.B’>,
<class ‘main.E’>, <class ‘main.C’>, <class ‘main.A’>, <class ‘object’>]

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

子类会先于父类被检查
多个父类会根据它们在列表中的顺序被检查
如果对下一个类存在两个合法的选择,选择第一个父类
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,那么属性的查找方式有两种,分别是:深度优先和广度优先

经典类与新式类
#在python2中-》经典类:没有继承object的类,以及它的子类都称之为经典类
class Foo:
    pass

class Bar(Foo):
    pass

# #在python2中-》新式类:继承object的类,以及它的子类都称之为新式类
class Foo(object):
    pass

class Bar(Foo):
    pass

#在python3中-》新式类:一个类没有继承object类,默认就继承object
class Foo():
    pass
print(Foo.__bases__)

经典类按照深度优先

在这里插入图片描述
新式类按照广度优先
在这里插入图片描述
测试结果:A-B1-C1-D1-E1-B2-C2-D2-E2-B3-C3-D3-E3-F-object
测试结果:A-B1-C1-D1-E1-B2-C2-D2-E2-B3-C3-D3-E3-F-object

在子类中调用父类的方法

在子类派生出的新的方法中重用父类的方法,有两种实现方式

  • 方式一:指名道姓(不依赖继承)
class Hero:
    def __init__(self,nickname,life_value,aggresivity):
        self.nickname=nickname
        self.life_value=life_value
        self.aggresivity=aggresivity
    def attack(self,enemy):
        enemy.life_value-=self.aggresivity

class Garen(Hero):
    camp='Demacia'
    def attack(self,enemy):
        Hero.attack(self,enemy) #指名道姓
        print('from Garen Class')

在子类重名函数中,直接调用父类函数实现:Hero.attack
调用父类__init__方法:

class Hero:
    def __init__(self,nickname,life_value,aggresivity):
        self.nickname=nickname
        self.life_value=life_value
        self.aggresivity=aggresivity
    def attack(self,enemy):
        enemy.life_value-=self.aggresivity
class Garen(Hero):
    camp='Demacia'
    def __init__(self,nickname,life_value,aggresivity,weapon):
        # self.nickname=nickname
        # self.life_value=life_value
        # self.aggresivity=aggresivity
        Hero.__init__(self,nickname,life_value,aggresivity)

        self.weapon=weapon

    def attack(self,enemy):
        Hero.attack(self,enemy) #指名道姓
        print('from Garen Class')

注意:1、调用父类__init__方法就跟普通调用函数一样,需要传入对应的参数
    2、执行完父类__init__后,可以新增子类的功能,这样可以减少重复代码
    3、以上功能都是直接调用父类的函数功能,不依赖继承的关系

  • 方式一:super()(依赖继承)
class Hero:
    def __init__(self,nickname,life_value,aggresivity):
        self.nickname=nickname
        self.life_value=life_value
        self.aggresivity=aggresivity
    def attack(self,enemy):
        enemy.life_value-=self.aggresivity


class Garen(Hero):
    camp='Demacia'

    def attack(self,enemy):
        super(Garen,self).attack(enemy) #依赖继承
        print('from Garen Class')

super()方法是依赖于继承,生成一个特殊父类的对象,然后就可以利用这个父类的对象直接调用父类中attack的函数属性
公式——super(子类的名称,self)也可以缩写为super()

调用父类__init__方法:

class Hero:
    def __init__(self,nickname,life_value,aggresivity):
        self.nickname=nickname
        self.life_value=life_value
        self.aggresivity=aggresivity
    def attack(self,enemy):
        enemy.life_value-=self.aggresivity
class Garen(Hero):
    camp='Demacia'
    def __init__(self,nickname,life_value,aggresivity,weapon):
        # super(Garen,self).__init__(nickname,life_value,aggresivity)
        super().__init__(nickname,life_value,aggresivity)
        self.weapon=weapon
  • 需要注意的是:super()是依赖继承,但是本质上是遵循MRO顺序向后寻找函数属性
class A:
    def f1(self):
        print('from A')
        super().f1()
class B:
    def f1(self):
        print('from B')
class C(A,B):
    pass
    
print(C.mro())
#[<class '__main__.C'>,
# <class '__main__.A'>,
# <class '__main__.B'>,
# <class 'object'>]

c=C()
c.f1()
输出
from A
from B

这段函数逻辑顺序:C类的MRO查找顺序是C-A-B-object
首先c=C()产生一个C类的对象,c.f1()调用f1函数先从对象本身,到C类,到A类才找到f1函数,执行from A,接着super().f1()会调用上级类中的f1函数,所以执行了B类中的f1打印from B,这里A其实没有继承B,但是super()只会寻找MRO下级的顺序

组合

  • 组合解决的是什么有什么的关系,例如老师有教书课程的关系,他们不存在老师是课程的关系,因此无法使用继承关系,在编程中解决重复代码的问题就用到组合
  • 组合其实就是定义一个类,这个类中的属性可以指向另一个类中产生的对象
  • 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
class People:
    school = "Luffycity"
    def __init__(self,name,age):
        self.Name = name
        self.Age = age

class Teacher:
    school = "Luffycity"
    def __init__(self,name,age,level):
        People.__init__(self,name,age)
        self.Level = level

class Student:
    school = "Luffycity"
    def __init__(self,name,age,class_time):
        People.__init__(self,name,age)
        self.Class_time = class_time

Teacher1 = Teacher("Alex",28,10)
Student1 = Student("杨飞",18,"weekend")

#当我想给老师加上教的课程标签时,可以创建一个课程类,与老师组合到一起
class Course:
    def __init__(self,course_name,course_price):
        self.course_name = course_name
        self.course_price = course_price
    def tell_info(self):
        print("课程名称:%s  课程价格:%s"%(self.course_name,self.course_price))

Course1 = Course("Python",10000)    #生成一个课程
Teacher1.Course = Course1   #将Course1这个对象赋值给老师一个新增的变量Course,代表老师教的课程
Student1.Course = Course1   #将Course1这个对象赋值给学生一个新增的变量Course,代表学生学的课程

Teacher1.Course.tell_info()	#执行Course内的tell_info函数,打印出课程信息
Student1.Course.tell_info()
输出
课程名称:Python  课程价格:10000
课程名称:Python  课程价格:10000

抽象类与归一化

什么是接口

接口指的是:自己提供给使用者来调用自己功能的方式\方法\入口

为何要用接口

接口提取了一群类共同的函数,可以把接口当做一个函数的集合定义一个新的父类(包括了子类共同的函数),然后让子类去实现接口中的函数。
归一化——就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样
归一化的优点:

  1. 比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后有本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
  2. 再比如:我们定义了人,猪,狗都有跑的功能,但是人类中函数名叫walk,猪类中函数名叫run,狗类中函数名叫zou,当用户在调用的时候需要记住人是walk,猪是run,狗是zou,那么我们可以定义一个父类Animal将走路这个功能的函数名统一为walk,这样继承这个父类的子类中都被限制必须有一个叫walk的函数属性
抽象类与普通类的区别
  • 抽象类只能被继承,不能实例化
  • 抽象类的功能只是用来规范子类
  • 抽象类内的函数名称不能被执行
import abc  #使用模块

class Animal(metaclass=abc.ABCMeta):	#需要引用metaclass=abc.ABCMeta
    @abc.abstractmethod #装饰器引用函数
    def run(self):
        pass    #归一化的类中不能执行函数
    @abc.abstractmethod
    def eat(self):
        pass

class People(Animal):
    def run(self):
        print("People is walking")
    def eat(self):
        print("People is eating rice")

class Dog(Animal):
    def run(self):
        print("Dog is walking")
    def eat(self):
        print("Dog is eating bone")

people1 = People()
dog1 = Dog()

people1.eat()
dog1.eat()

注意:

  1. 需要导入abc模块
  2. 需要装饰器引用abc.abstractmethod的功能,用于限制继承父类的子类中必须要有run的函数名
  3. 需要在父类Animal中加入metaclass=abc.ABCMeta
  4. 如果子类中没有run或者eat函数,就会报错

多态与多态性

多态

多态指的是一类事物有多种形态,比如动物有多种形态:人,狗,猪

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')
多态性
一、什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)

多态性是指在不考虑对象的类型的情况下使用实例,多态性分为静态多态性动态多态性

静态多态性:如任何类型(字符串、数字、列表)都可以用运算符+进行运算

动态多态性:如下

peo=People()
dog=Dog()
pig=Pig()

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

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()
func(peo)	#只用一个fun()接口就能调用
func(dog)
二、多态性的好处
  1. 增加了程序的灵活性
    以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
  2. 增加了程序额可扩展性
     通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
class Cat(Animal): #属于动物的另外一种形态:猫
    def talk(self):
        print('say miao')

def func(animal): #对于使用者来说,自己的代码根本无需改动
    animal.talk()

cat1=Cat() #实例出一只猫
func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能

# 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
鸭子类型

Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法

#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass
    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pass

例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系

#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))

#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()

len(s)
len(l)
len(t)

封装

类中如何隐藏属性

类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class A:
    __x=1
    """在数据属性名称前加__就可以隐藏,但其本质是将__x改为_A__x存进了类的名称空间"""
    def __init__(self,name):
        self.__name=name

    def __foo(self):
        print('run foo')
    """在函数属性名称前加__就可以隐藏,但其本质是将__foo改为_A__foo存进了类的名称空间"""
print(A.__dict__)
这种变形的特点
  • 在类外部无法直接obj.__AttrName
    因为变量名__x已经被系统改为_A__x,直接调用A.__x是无法导出,只有A._A__x才可以导出
  • 在类内部是可以直接使用:obj.__AttrName
    因为程序运行前,python在读取类的代码时,识别到__foo已经自动更改为_A__foo
    当读取下面函数def fun()使用到__foo时,已经自动更改函数名为_A__foo,已经同步不会影响使用
  • 子类无法覆盖父类__开头的属性
class Foo:
    def __func(self): #_Foo__func
        print('from foo')
		"""父类__func实际改名为_Foo_func"""
class Bar(Foo):
    def __func(self): #_Bar__func
        print('from bar')
        """子类__func实际改名为_Bar_func"""
这种变形需要注意的问题是
  • 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
  • 变形的过程只在类的定义时发生且仅发生一次,在定义后的赋值操作,不会变形
class B:
    __x=1
    def __init__(self,name):
        self.__name=name #self._B__name=name
B.__y=2
print(B.__y)
输出2
"""在类定义完成后赋值__y=2,系统并不会改名叫_B__y,所以可以直接用对象调用B.__y"""
  • 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的(即__fun)
class A:
    def foo(self):
        print('A.foo')

    def bar(self):
        print('A.bar')
        self.foo() #b.foo()

class B(A):
    def foo(self):
        print('B.foo')

b=B()
b.bar()
输出
A.bar
B.foo
"""b.bar()首先找到A类中的bar()输出A.bar,然后读取self.foo()实际就是b.foo(),那么就回到起点寻找foo(),对象本身没有,然后对象所属的类B中找到了,打印B.foo"""

上面的例子中执行self.foo()时会回到子类中调用,如果就像想它用父类中的foo方法,就可以用__foo实现,如下:

class A:
    def __foo(self): #改名为_A__foo
        print('A.foo')

    def bar(self):
        print('A.bar')
        self.__foo() #改名为self._A__foo()

class B(A):
    def __foo(self): #改名为_B__foo
        print('B.foo')

b=B()
b.bar()
输出
A.bar
A.foo
"""与上面的区别:当执行到self.__foo()时,实际的代码是self._A__foo(),所以不会执行到子类中的_B__foo()函数"""
封装的意义
一、封装数据属性:明确的区分内外,控制外部对隐藏属性的操作行为

将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制

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('egon',18)
t.tell_info()

t.set_info('egon',19)
t.tell_info()
二、封装方法(函数属性):隔离复杂度

对于用户不需要知道的各环节方法进行隐藏,只提供一个供用户调用的接口

lass 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()

a=ATM()
a.withdraw()
封装与可扩展性
class Room:
    def __init__(self,name,owner,weight,length,height):
        self.name=name
        self.owner=owner

        self.__weight=weight
        self.__length=length
        self.__height=height

    def tell_area(self):
        return self.__weight * self.__length * self.__height

r=Room('卫生间','alex',10,10,10)

print(r.tell_area())

用户只管调用一个tell_area接口,开发者可以设定类中既可以计算面积也可以计算体积,不会影响到用户的调用方法

特性(Property)
什么是property

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
利用装饰器property的方法将一个函数本来的调用方式p.bmi()改为p.bmi,让用户更加方便,更加统一的调用方式

property的使用
'''
BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
'''
class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height

    @property	"""加上装饰器,用户调用p.bmi()的方式可以改为p.bmi"""
    def bmi(self):
        return self.weight / (self.height ** 2)

p=People('egon',75,1.81)

print(p.bmi)
"""因为bmi指数更像name一样是一个人的数据属性,用户习惯性的调用方式并不会以函数的方式调用"""
property的扩展功能
class People:
    def __init__(self,name):
        self.__name=name

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

    @name.setter
    def name(self,val):
        if not isinstance(val,str):
            print('名字必须是字符串类型')
            return
        self.__name=val

    @name.deleter
    def name(self):
        print('不允许删除')

p=People('egon')

p.name
p.name='EGON'
del p.name

p.name本质上是p.name(),由于装饰器@property的效果,可以直接p.name调用
p.name = ‘EGON’,这种赋值操作会触发装饰器@name.setter,进行对封装数据的修改
del p.name,这种删除操作会触发装饰器@name.deleter,进行对封装数据的删除

绑定方法与非绑定方法

在类内部定义的函数,分为两大类:
一、绑定方法:绑定谁,就应该由谁来调用,谁来调用就把调用者当做第一个参数自动传入
  1. 绑定到对象的方法
    在类内定义的没有被任何装饰器修饰的函数
class People:
    def __init__(self,name):
        self.name = name

    def tell(self):
        print(self.name)
Peo = People("杨飞")
Peo.tell()
  1. 绑定到类的方法
    在类内定义的被装饰器classmethod修饰的方法
class People:
    def __init__(self,name):
        self.name = name

    @classmethod
    def Foo(cls):
        print("Hello")

People.Foo()
二、非绑定方法:没有自动传值的功能,就是一个普通的函数,对象和类都可以使用

既不与类也不与对象绑定

class People:
    def __init__(self):
        pass

    @staticmethod
    def Foo(x,y):
        print(x+y)
P = People()

People.Foo(1,2)
P.Foo(1,2)
输出
3
3
绑定方法与非绑定方法的使用
  1. 什么情况下需要绑定到对象
class People:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def tell_info(self):
        print("Name:%s Age:%s Sex:%s" %(self.name,self.age,self.sex))
    """这里每个人的信息不同,程序需要将对象的个人信息传入,所以使用绑定对象的方法"""
  1. 什么情况下需要绑定到类
    事先创建一个test2.py
name = "杨飞"
age = 18

当实例化一个对象需要从另外一个文件中导入的时候,我们不希望用户手动填写姓名年龄等参数,就需要使用函数封装,让用户调用更方便

import test2
class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def tell_info(self):
        print("Name:%s Age:%s" %(self.name,self.age))
    
    @classmethod
    def from_test2(cls):	"""这里要用绑定到类的方法可以避免People如果改名字不会影响类的名字作为cls参数传进来"""
        obj = cls(test2.name,test2.age)
        return obj
        
Peo = People.from_test2()
Peo.tell_info()
输出
Name:杨飞 Age:18
  1. 什么情况下需要使用非绑定方法
    如果我想给实例化的每个对象加上一个id,我们可以利用time模块生成不同的时间戳,再进行md5模块转化成不同的密码
import time
import hashlib
class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        self.id = self.creat_id()
        
    @staticmethod
    def creat_id():
        m = hashlib.md5(str(time.time()).encode("utf-8"))
        return m.hexdigest()
	"""创建id是一个独立的功能,它不需要对象也不需要类的调用,只需要执行并返回一段密码
	因此使用非绑定方法"""

Peo = People("杨飞",18)
print(Peo.id)

反射

是指通过字符串映射到对象的属性
主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)
如下:我希望通过用户输入查询内容来调用到对象的属性

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

obj = People("杨飞",18)
choice = input(">>>:")  #这里的choice等于字符串形式的'name'
print(obj.choice)   #obj这个对象并没有名叫choice的属性,会报错
四个可以实现自省的函数
1.hasattr(object,name)

判断object中有没有一个name字符串对应的方法或属性,有返回True,没有则返回False

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def talk(self):
        print("%s is talking"%self.name)
obj = People("杨飞",18)

print(hasattr(obj,"name"))
print(hasattr(obj,"xxxxx"))
输出
True
False
2.getattr(object, name, default=None)

得到对象object的name属性并返回该值,如果没有找到则返回None
如果不填写default,那么找不到就会报错

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def talk(self):
        print("%s is talking"%self.name)
obj = People("杨飞",18)

#print(getattr(obj,"xxxx"))	#报错AttributeError: 'People' object has no attribute 'xxxx'
print(getattr(obj,"xxxx","没有找到"))   #None也可以写你想要返回的内容
print(getattr(obj,"name",None))
输出
没有找到
杨飞
3.setattr(obj, name, value)

将一个字符串的name设置成为属性等于value

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def talk(self):
        print("%s is talking"%self.name)
obj = People("杨飞",18)

setattr(obj,"sex","male")  
"""本质上是obj.sex = male,但是setattr可以接受sex为字符串形式"""
print(obj.sex)
输出
male
4.delattr(obj, name)

删除某个属性

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def talk(self):
        print("%s is talking"%self.name)
obj = People("杨飞",18)

delattr(obj,"age")  #本质上是del obj.age
print(obj.__dict__)
注意:类也是一个对象,适用以上四种方法
class People:
    sex = "male"
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def talk(self):
        print("%s is talking"%self.name)
obj = People("杨飞",18)

setattr(obj,"sex","men") 
print(obj.sex)
输出
men
反射的应用
class Service:
    def run(self):
        while True:
            cmd = input(">>>").strip()	#根据用户输入的指令
            if hasattr(self,cmd) :	#判断用户输入指令是否在对象的属性中
                fun = getattr(self,cmd)  """注意fun获得的只是一个函数属性的地址,需要在下一步加()进行调用"""
                fun()
    def get(self):
        print("get:.....")
    def put(self):
        print("put:.....")

obj = Service()
obj.run()

内置方法

一、isinstance(obj,cls)和issubclass(sub,super)

isinstance(obj,cls)检查是否obj是否是类 cls 的对象

class Foo(object):
    pass
obj = Foo()
isinstance(obj, Foo)

issubclass(sub, super)检查sub类是否是 super 类的派生类

class Foo(object):
    pass
class Bar(Foo):
    pass
issubclass(Bar, Foo)
二、item系列

把对象变成像字典一样操作方式的方法:

  1. def__getitem__(self,item)
  2. def__setitem__(self, key, value):
  3. def__delitem__(self, key):
class Foo:
    def __getitem__(self,item):
        return self.__dict__.get(item)  #字典用getitem的好处在于查找不到key不会报错,会返回None
    def __setitem__(self, key, value):
        self.__dict__[key] = value
    def __delitem__(self, key):
        del self.__dict__[key]

obj = Foo()
#查看属性
obj["name"]     """用字典的方式操作定义一个key,本质上是obj.__getitem__(name)"""
#设置属性
obj["name"] = "alex"    """本质上是obj.__setitem__("name","alex")"""
#删除属性
del obj["name"]     """本质上是obj.__delitem__("name")"""
三、__str__方法

让print(obj)的时候打印的不是obj的内存地址,而是你需要打印出对象的属性

d = dict({"name":"alex"})   #d是dict类实例化出的一个对象

class Foo():
    pass
obj = Foo()     #obj是Foo类实例化出的一个对象

print(d)
print(obj)
输出
{'name': 'alex'}	
<__main__.Foo object at 0x02EEF0D0>
"""因为dict类在python自带定义类的时候已经将打印修改为字典的元素这样有用的信息
不加方法的话就会打印出一个对象的内存地址(没有实质用途)"""

用__str__方法解决:

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def __str__(self):
        return "Name:%s  Age:%s"%(self.name,self.age)

obj = People("杨飞",18)
print(obj)
输出
Name:杨飞  Age:18

__str__方法注意

  • 只有对象遇到print(obj)的时候才会执行
  • 函数必须设置return,并且return的内容必须为字符串格式
三、__del__方法

在对象被删除的时候会先触发__del__函数再删除对象

f = open("test2.py")

"""f是一个变量名存储在python的名称空间中
open("test2.py")是由操作系统读取硬盘的数据并赋值给f,它占用的是系统内存"""

#f.close()
"""如果我们不写close(),就会导致程序运行完成后,open的文件继续占用系统的内存"""

跟对象相关联的一些数据资源回收操作可以写进__del__函数里,及时地回收不需要的数据

class Open:
    def __init__(self,filename):
        self.filename = filename
        print("Open %s"%self.filename)

    def __del__(self):
        print("回收与对象相关联的资源")

f = Open("作业")

print("------程序到这里结束,python马上就要删除变量f了------")
执行结果:
Open 作业
------程序到这里结束,python马上就要删除变量f了------
回收与对象相关联的资源

程序运行结束后会自动删除变量f(它也是一个对象),在删除前会执行__del__的函数,打印了相应的结果

元类

一、exec(obj,globals,locals)

exec:三个参数,将字符串形式的命令执行
参数一:字符串形式的命令
参数二:全局作用域(字典形式),如果不指定,默认为globals()
参数三:局部作用域(字典形式),如果不指定,默认为locals()

g = {"x":1,"y":2}   #定义一个全局作用域
l = {}  #定义一个局部作用域
exec("""
global x,m  
x = 10
m = 100

z = 3
""",g,l)	记得传入g,l作为参数二和参数三
print(g)	#声明的global x,m会对传入的全局g进行操作
print(l)	#未声明global的变量z会对传入的局部l进行操作
二、一切皆对象

所有的对象都可以怎么用?

  1. 都可以被引用x = obj
  2. 都可以当做函数的参数传入
  3. 都可以当做函数的返回值
  4. 都可以当做容器类的元素,l = [func,obj,time,1]
类也是对象,Foo=type(....),它是type类实例化的一个对象
class Foo:
    pass

obj=Foo()
print(type(obj))	输出<class '__main__.Foo'>
print(type(Foo))	输出<class 'type'>
三、什么是元类?

产生类的类称之为元类,默认所有用class定义的类,他们的元类都是type

方式一:使用class关键字

class Chinese(object):	"""本质上是Chinese = type()"""
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print('%s is talking' %self.name)

方式二:就是手动模拟class创建类的过程:将创建类的步骤拆分开,手动去创建
定义类有三要素:类名、类的基类们(继承)、类的名称空间

class_name='Chinese'	定义类名
class_bases=(object)	定义类的继承来自于object类(以元组的形式),如果Chinese继承了类A,这里就写class_bases = (A,object)

class_body="""		
country='China'

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

def talk(self):
    print('%s is talking' %self.name)
"""		将类里面的代码作为类体

class_dic={}	定义类的名称空间为空集合
exec(class_body,globals(),class_dic)	将字符串形式的代码exec操作存入名称空间

Chinese = type(class_name,class_bases,class_dic)	type是元类,它可以生成类,需要传入三个参数(类名,类的继承,类的名称空间)

自定义元类控制类的行为

class Mymeta(type):		#由于type作为元类会封装很多函数方法,那么Mymeta作为一个自创的元类需要在新增自定义方法以外继承type的方法
    def __init__(self,class_name,class_bases,class_dict):	#一旦执行Chinese类的时候就会追溯到创建它的元类是Mymeta,那么就会执行__init__方法
        if not class_name.istitle():
            raise TypeError("类名首字母必须大写")
        super(Mymeta,self).__init__(class_name,class_bases,class_dict)

        if "__doc__" not in class_dict or not class_dict["__doc__"].strip():	
            raise TypeError("类中必须加注释(类似'''xxx注释xxx''')")

class Chinese(object,metaclass=Mymeta):		#metaclass=Mymeta的意思是将Chinese定义为Mymeta创建的类,也就是Mymeta是一个元类(创建类的类叫元类)
    '''x'''
    country='China'

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

    def talk(self):
        print('%s is talking' %self.name)

#Chinese = Mymeta(class_name,class_bases,class_dict)  其实就是复制上面讲的type作为元类是如何创造一个类的Chinese = type(class_name,class_bases,class_dic)

以上代码解析:
1、 metaclass=Mymeta的意思是将Chinese定义为Mymeta创建的类,也就是Mymeta是一个元类(创建类的类叫元类),它默认为metaclass=type
2.、Chinese = Mymeta(class_name,class_bases,class_dict)是模仿type作为一个元类是如何创建类的,需要传入三个参数
3、 class Mymeta(type): 由于type作为元类会封装很多函数方法,那么Mymeta作为一个自创的元类需要在新增自定义方法以外继承type的方法
4、Mymeta的__init__方法需要传入参数,一旦执行Chinese类的时候就会追溯到创建它的元类是Mymeta,那么就会执行__init__方法
5、class_name.istitle() 判断首字母是否大写且其他字母为小写
6、__doc__是在名称空间中的一个key(document),它的value是取自你是否在创建类或者函数时添加这个类的相关注释,需要用三个引号标注
7、raise TypeError(“你想要的字符串”) raise可以主动在系统中报错,并写明报错原因

自定义元类控制类的实例化行为

__call__方法:

obj = Foo(),obj作为一个对象本身并没有调用的方法obj(),但当类中加入__call__方法后,可以在obj后面加括号obj()调用的时候执行__call__下面的代码

class Foo():
    def __call__(self,*args,**kwargs):
        print(self)
        print(args)
        print(kwargs)
obj = Foo()
obj(1,2,3,a=1,b=2,c=3)

输出
<__main__.Foo object at 0x01B6F040>
(1, 2, 3)
{'a': 1, 'b': 2, 'c': 3}

总结——元类内部应该也有一个__call__方法,会在调用创建的类的时候执行
Foo(1,2,x=1) 本质上是Foo._ call_ (Foo,1,2,x=1)

如何通过元类控制实例化:

思路——通过控制类实例化其实就是对象本身执行__call__方法的本质,可以在__call__内部自定义方法来控制实例化的行为
__call__在进行实例化分为三步骤:
1、创建一个空的对象
2、初始化对象,即执行对象所属类的__init__方法
3、 返回对象

class Mymeta(type):		
    def __call__(self, *args, **kwargs):
    	#第一步:创建一个空对象,运用object.__new__方法
        obj = object.__new__(self)
        #第二步:初始化对象,执行实例化对象的类Chinese的__init__方法
        self.__init__(obj,*args,**kwargs)
        #第三步:返回对象
        return obj

class Chinese(object,metaclass=Mymeta):
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def talk(self):
        print("运行talk功能")

ch1 = Chinese("alex",18)
print(ch1.__dict__)

以上代码解析:
1、首先指定Chinese类的元类是Mymeta: metaclass = Mymeta
2、在Mymeta这个元类中自定义__call__方法来自定义实例化行为
3、object. _ new_(类名) 是创建空对象的方法,括弧中应填写这个对象来自的类名,_ call_(self, *args, **kwargs):已经将类名作为self传入,所以可以直接obj = object.new(self)
4、self._init _ (obj,*args,**kwargs)就是执行对象所属类的__init__方法,self就是所属类Chinese,括弧中传入的参数需要原封不动的传入

自定义元类控制类的实例化行为的应用:

单例模式:

在实例化中会遇到创建的对象所有属性都一样,但是占用着不同的内存空间,导致资源浪费

class Foo:
    def __init__(self):
        self.name = "alex"
        self.age = 18
obj1 = Foo()
obj2 = Foo()
print(obj1)
print(obj2)
print(obj1.__dict__)
print(obj2.__dict__)

输出
<__main__.Foo object at 0x033FF0D0>		内存地址是不一样的
<__main__.Foo object at 0x033FF388>
{'name': 'alex', 'age': 18}		对象是一样的
{'name': 'alex', 'age': 18}

当重复对象过多时,就需要进行单例化:

  • 方法一:直接定义一个绑定类的函数singleton单例化
class Foo:
    __isinstance = None

    def __init__(self):
        self.name = "alex"
        self.age = 18

    @classmethod		因为是绑定到类的函数,需要装饰classmethod
    def singleton(cls):
        if cls.__isinstance == None:		代表还没有创建过对象
            obj = cls()		调用类创建一个对象
            cls.__isinstance = obj		将flag赋值为第一个对象
        return cls.__isinstance		返回flag,遇到下一次创建相同对象的时候不会执行if判断直接返回上次生成的对象
        
obj1 = Foo().singleton()	调用的时候需要走singleton方法,以此规范生成不重复的对象
obj2 = Foo().singleton()

print(obj1)
print(obj2)
print(obj1.__dict__)
print(obj2.__dict__)

输出
<__main__.Foo object at 0x01AAF100>		地址变成一样的
<__main__.Foo object at 0x01AAF100>
{'name': 'alex', 'age': 18}
{'name': 'alex', 'age': 18}
  • 方法二:通过元类实现
class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dic):
        self.__isinstance = None		代表还没有创建过对象
        super(Mymeta,self).__init__(self)
    def __call__(self, *args, **kwargs):		在实例化自动调用__call__的过程实现单例
        if self.__isinstance == None:
            obj = object.__new__(self)
            self.__init__(obj)
            self.__isinstance = obj
        return self.__isinstance


class Foo(object,metaclass=Mymeta):

    def __init__(self):
        self.name = "alex"
        self.age = 18

obj1 = Foo()
obj2 = Foo()
print(obj1)
print(obj2)
print(obj1.__dict__)
print(obj2.__dict__)

面向对象的软件开发

面向对象的软件工程包括下面几个步骤:
1.面向对象分析(object oriented analysis ,OOA)
2 面向对象设计(object oriented design,OOD)
3 面向对象编程(object oriented programming,OOP)
4 面向对象测试(object oriented test,OOT)
5 面向对象维护(object oriendted soft maintenance,OOSM)

领域建模

最后我们总结出领域建模的三字经方法:找名词、加属性、连关系
找名词
who : 学员、讲师、管理员

用例:

  1. 管理员创建了 北京 和 上海 两个校区
  2. 管理员 创建了 Linux Python Go 3个课程
  3. 管理员 创建了 北京校区的Python 16期, Go开发第一期,和上海校区的Linux 36期班级
  4. 管理员 创建了 北京校区的学员小晴 ,并将其 分配 在了 班级 python 16期
  5. 管理员 创建了讲师Alex , 并将其分配 给了 班级 python 16期 和全栈脱产5期
  6. 讲师 Alex 创建 了一条 python 16期的上课纪录Day6
  7. 讲师 Alex 为Day6这节课 所有的学员 批了作业,小晴得了A, 李磊得了C-, 严帅得了B
  8. 学员小晴 在 python 16 的 day6里 提交了作业
  9. 学员李磊 查看了自己所报的所有课程
  10. 学员 李磊 在 查看了 自己在 py16期 的成绩列表 ,然后自杀了
  11. 学员小晴 跟 讲师 Alex 表白了

名词列表:
管理员、校区、课程、班级、上课纪录、作业、成绩、讲师、学员

加属性
在这里插入图片描述
连关系
有了类,也有了属性,接下来自然就是找出它们的关系了。
在这里插入图片描述

异常处理

一、什么是异常:

异常是错误发生的信号,一旦程序出错,并且程序没有处理这个错误,那个就会抛出异常,并且程序的运行随之终止

二、错误分为两种:
1.语法错误:在程序执行前就要立刻改正过来

如:
print(‘xxxx’
if 1 > 2

2.逻辑错误
ValueError
# int('aaa')
NameError
# name
IndexError
# l=[1,2,3]
# l[1000]
KeyError
# d={}
# d['name']
AttributeError属性错误
# class Foo:
#     pass
#
# Foo.xxx
ZeroDivisionError 用1除以0
# 1/0
TypeError:int类型不可迭代
# for i in 3:
#     pass
KeyboardInterrupt 程序被手动暂停
# import time
# time.sleep(1000)
IOError 输入/输出异常;基本上是无法打开文件
三、异常处理
1.如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防
AGE=10
while True:
    age=input('>>: ').strip()
    if age.isdigit(): #只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的
        age=int(age)
        if age == AGE:
            print('you got it')
            break
2.如果错误发生的条件是不可预知的,则需要用到try…except:在错误发生之后进行处理
try:
    f=open('a.txt','r',encoding='utf-8')

    print(next(f),end='')	next(f)是使用迭代器,等于f.readline()
    print(next(f),end='')
    print(next(f),end='')
    print(next(f),end='')

    print(next(f),end='')
    print(next(f),end='')
    print(next(f),end='')

    f.close()
except StopIteration:		不可迭代的错误
    print('出错啦')

print('====>1')
print('====>2')
print('====>3')

try:后面正常的写代码,除非遇到except后面的错误再进行下一步操作

三、try…except…详细用法
1.异常类只能用来处理指定的异常情况,如果非指定异常则无法处理

s1 = ‘hello’
try:
 int(s1)
except IndexError as e: # 未捕获到异常,程序直接报错
  print e

2.多分支

多分支:被监测的代码块抛出的异常有多种可能性,并且我们需要针对每一种异常类型都定制专门的处理逻辑

try:
    print('===>1')
    # name
    print('===>2')
    l=[1,2,3]
    # l[100]
    print('===>3')
    d={}
    d['name']
    print('===>4')

except NameError as e:	
    print('--->',e)

except IndexError as e:
    print('--->',e)

except KeyError as e:
    print('--->',e)

except NameError as e:是将Error的名字赋值给e

3.万能异常:Exception,能够捕捉所有异常(语法synax异常除外)

被监测的代码块抛出的异常有多种可能性,并且我们针对所有的异常类型都只用一种处理逻辑就可以了,那就使用Exception

try:
    print('===>1')
    # name
    print('===>2')
    l=[1,2,3]
    l[100]
    print('===>3')
    d={}
    d['name']
    print('===>4')

except Exception as e:
    print('异常发生啦:',e)
4.也可以在多分支后来一个Exception
5.异常的其他结构:else、finally
try:
    print('===>1')
    # name
    print('===>2')
    l=[1,2,3]
    # l[100]
    print('===>3')
    d={}
    d['name']
    print('===>4')

except NameError as e:
    print('--->',e)

except IndexError as e:
    print('--->',e)

except KeyError as e:
    print('--->',e)

except Exception as e:
    print('统一的处理方法')

else:
    print('在被检测的代码块没有发生异常时执行')

finally:
    print('不管被检测的代码块有无发生异常都会执行')

else——在被检测的代码块没有发生异常时执行
finally——不管被检测的代码块有无发生异常都会执行,通常用于回收资源

finally示例:

try:
    f=open('a.txt','r',encoding='utf-8')
    print(next(f))
    print(next(f))
    print(next(f))
    print(next(f))

    print(next(f))
    print(next(f))
finally:
    f.close()	不论代码报不报异常,都会执行f.close()
6.主动触发异常:raise 异常类型(值)
class People:
    def __init__(self,name,age):
        if not isinstance(name,str):
            raise TypeError('名字必须传入str类型')
        if not isinstance(age,int):
            raise TypeError('年龄必须传入int类型')

        self.name=name
        self.age=age

p=People('egon',18)
7.自定义异常类型

自定义的异常类型属于类,它需要继承BaseException的异常类

class MyException(BaseException):
    def __init__(self,msg):
        super(MyException,self).__init__()
        self.msg=msg

    def __str__(self):
        return '<%s>' %self.msg

raise MyException('我自己的异常类型') #print(obj)

raise在执行的时候会自动进行打印异常的对象,__str__可以将print(obj)由obj的内存地址改为obj的属性

8.断言:assert 条件
info={}
info['name']='egon'
info['age']=18
第一部分代码:创立信息的集合
# if 'name' not in info:
#     raise KeyError('必须有name这个key')
#
# if 'age' not in info:
#     raise KeyError('必须有age这个key')

assert ('name' in info) and ('age' in info)
第二部分代码:判断info集合的信息完整性
if info['name'] == 'egon' and info['age'] > 10:
    print('welcome')
第三部分代码:依据以上数据进行下一步操作

当后面程序需要依赖前面程序得出的结果时,可以运用assert进行判断,若不符合就会报错

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值