一、组合
自定义类的对象作为另一个类的属性。
class Teacher:
def __init__(self,name,age):
self.name = name
self.age = age
t1 = Teacher('Bob',20)
print(type(t1.name), type(t1.age))
class Student:
# 学生可以有 老师 属性
def __init__(self, name, age, teacher):
self.name = name
self.age = age
# 自定义类的对象作为类的属性:组合
self.teacher = teacher
# 创建一个学生
# 创建一个学生
stu = Student('Bob', 18, t1)
print(stu.__dict__)
# 学生的老师年龄和姓名
print(stu.name)
print(stu.teacher)
print(stu.teacher.name)
print(stu.teacher.age)
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
class People:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
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 Teacher(People):
def __init__(self,name,age,sex,job_title):
People.__init__(self,name,age,sex)
self.job_title=job_title
self.course=[]
self.students=[]
class Student(People):
def __init__(self,name,age,sex):
People.__init__(self,name,age,sex)
self.course=[]
egon=Teacher('egon',18,'male','沙河霸道金牌讲师')
s1=Student('牛榴弹',18,'female')
python=Course('python','3mons',3000.0)
linux=Course('python','3mons',3000.0)
#为老师egon和学生s1添加课程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)
#为老师egon添加学生s1
egon.students.append(s1)
#使用
for obj in egon.course:
obj.tell_info()
例子:继承与组合
二、继承
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
子类会“”遗传”父类的属性,从而解决代码重用问题
将所有共同的属性与方法抽离出,形成父类
父类是多个有共同点的普通类抽离共有属性与方法形成的类
class People:
def __init__(self,name):
self.name = name
def eat(self):
print(self.name + '在吃饭')
class Student(People):
identify = '学生'
# def __init__(self, name):
# self.name = name
#
# def eat(self):
# print(self.name + '在吃饭')
student = Student('Bob')
student.eat()
class Teacher(People):
# def __init__(self, name):
# self.name = name
#
# def eat(self):
# print(self.name + '在吃饭')
pass
teacher = Teacher('Ben')
teacher.eat()
class Leader(People):
# def __init__(self, name):
# self.name = name
#
# def eat(self):
# print(self.name + '在吃饭')
pass
leader = Leader('Yang')
leader.eat()
print(Student.identify) # 只是自身类拥有
# print(Teacher.identify) # 父类没有,其他类就没有
print(Leader.__bases__) # (<class '__main__.People'>,)
继承的语法:
class 类名(父类名):
pass
class A:
pass
print(A.__bases__) # object
经典类与新式类
1.只有在python2中才分新式类和经典类,python3中统一都是新式类
2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
3.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
2.1、继承关系
1、父类所有未封装的属性和方法,子类都能访问
2、父类所有封装的属性和方法,子类都不能访问
-- 在外界通过子类和子类对象,都不能访问
-- 在子类内部也不能访问
class Sup:
__num = 10
def __init__(self, name):
self.__name = name
@property
def name(self):
print(self)
return self.__name
@classmethod
def __c_fn(cls):
print(cls,'c fn')
def __o_fn(self):
print(self.name, 'o fn')
class Sub(Sup):
def test(self):
print(self)
print(self.__name)
# Sub.__c_fn() # 无法访问父类的私有属性
sub = Sub('sss')
print(sub.name)
# sub.test() # 无法访问
2.2、有继承关系下的属性查找顺序
有继承关系下的属性查找顺序
1、优先找自身,自身没有再找父类
2、父类没有继续往父类的父类找
3、一直找到最*的父类,如果没有就报错
2.3、方法的重写
先写子类,抽离出父类
先写父类,派生出子类
class Sup:
num = 10
def test(self):
print('test sup')
class Sub(Sup):
num = 100
# 先写好父类的方法,由于父类方法的功能不满足子类需求,
# 子类可以重写父类方法:方法名与父类相同,自定义方法的实现体
def test(self):
print('test sub')
print(Sub.num) # 100
Sub().test() # test sub
2.4、方法的重用
有时,单纯的继承父类不能满足需求,但是重写又要用到父类,因此可以通过super()关键字进行方法重用
class Sup:
def test(self):
print('sup>>>',self)
print('test sup')
class Sub(Sup):
def test(self):
# python2中写法
# super(Sub, self).test()
# python3中简化写法
super().test()
print('sub>>>',self)
print('test sub')
Sub().test() # 既执行父类,又执行子类
__init__结合super()使用:
# 人类:只需要初始化 - name
# 老师: 要初始化 - name salary
# 学生: 要初始化 - name grade
class People:
def test(self):
print(self)
def __init__(self,name):
self.name = name
class Teacher(People):
# 有继承关系下,只要名字相同,即使参数不同,还是属于同一个方法
def test(self, num):
super().test()
print(num)
# 默认父级的__init__可以被继承过来,
# 但是会出现子类对象的属性比父类多
def __init__(self,name,salary):
super().__init__(name) # 父级有的共性功能通过super()交给父级做
self.salary = salary # 子类特有的自己来完成
Teacher('Bob',20000).test(10)
# 使用的时候,还是要使用自己的带参数的,不能使用父类不带参的
# (本质名字相同就是一个,优先查找自己的)
# Teacher('Bob',20000).test() # test() missing 1 required positional argument: 'num'
重点:super() 可以得到调用父级功能的对象,调用者还是子类对象
-- super()只能在子类的方法中使用
-- super()本质 super(子类类名, 当前对象)
-- super().父类普通方法 | super().__init__() | super()能调用父类所有可继承方法
了解:
-- java中存在真正的方法的重用
def fn():
pass
def fn(num):
pass
# fn()调用不传参时调用第一个fn
# fn(10)调用传入一个参数时调用第二个fn
2.5、多继承
2.5.1、简单的多继承
属性的查找顺序:优先找自己的,如果没有,按照继承先后查找父级
通过 类.mro()可以查看继承顺序
class A:
name = 'A'
num = 10
class B:
name = 'B'
count = 100
# 子类可以继承所有父类的所有可继承属性
class C(A, B): # 自己 => A => B
# name = 'C'
pass
print(C.num)
print(C.count)
print(C.name)
# 打印属性查找的顺序
print(C.mro())
2.5.2、复杂多继承
class A:
name = "A"
class B(A):
name = "B"
class C:
name = "C"
class D(C):
name = "D"
class E(B, D): # 先将B的所有父级们找完再找D的分支
name = "E"
print(E.mro()) # E => B => A => D => C
2.5.3、菱形继承
经典类:python2中才有,没有继承任何类的类, python2中深度优先
新式类:python2中直接或间接继承object的类,python3中所定义的所有类, python3中广度优先
三、接口思想
python中没有接口的语法,接口是一种思想。
接口是建立代码之间联系的桥梁,方便管理代码。
面向对象与面向过程分析:
清晰知道操作的功能,但不明确操作的具体对象
print(len('123'))
清晰知道操作的对象,但不明确具体的操作方法
print('123'.__len__())
接口类:用来定义功能的类,为继承他的子类提供功能
该类的功能方法不需要实现体,由继承他的子类自己去实现
# 提供所有宠物应该有的功能
class PetInterface:
def close_master(self):
pass
# 提供所有看门应该有的功能
class WatchInterface:
def watch_door(self):
pass
# 没有去继承PetInterface,WatchInterface的Dog就是普通的Dog类
# 但继承了PetInterface,该Dog就可以作为宠物狗,同理继承WatchInterface就可以作为看门狗
class Dog(PetInterface,WatchInterface):
def jiao(self):
pass
def chi(self):
pass
def pao(self):
pass
# 一定要重写接口的方法
pass
class Cat(PetInterface, WatchInterface):
pass
3.1、抽象类
抽象父类:拥有抽象方法(子类共有,但是父类不能有实现体)的父类
抽象方法:方法名是具体的,函数体没有写(需要在子类中重写)
注意点:有抽象方法的父类不能被实例化(假设能被实例化,就可以调用自己的抽象方法,没有任何意义)
实现抽象父类的语法:
import abc # abstract base class
class Sup(metaclass=abc.ABCMeta):
# 抽象父类中的抽象方法,在继承它的子类中必须有自己的实现体
# -- 抽象父类中的抽象方法实现体就没有意义,实现与不实现都是pass填充
@abc.abstractmethod
def func(self):
pass
class Sub(Sup):
def func(self):
# 必须重写父类的抽象方法
print('abstract')
# 案例
import abc
class Quan(metaclass=abc.ABCMeta):
def __init__(self, name):
self.name = name
# 共有方法,子类继承就可以了
def run(self):
print(self.name + 'running')
# 抽象方法:子类必须重写
@abc.abstractmethod
def chi(self): pass
@abc.abstractmethod
def jiao(self): pass
class Dog(Quan):
def kanmen(self):
print(self.name + '看门')
def chi(self):
super().chi()
print(self.name + '狗粮')
def jiao(self):
print('汪汪汪')
class Wolf(Quan):
def bulie(self):
print(self.name + '捕猎')
def chi(self):
print(self.name + '肉')
def jiao(self):
print('嗷嗷嗷')
dog = Dog('来福')
wolf = Wolf('常委')
dog.jiao()
wolf.jiao()
dog.run()
wolf.run()
了解:
抽象的类方法:
import abc
class Sup(metaclass=abc.ABCMeta):
@classmethod
@abc.abstractmethod
def func(cls): pass
class Sub(Sup):
@classmethod
def func(self):pass
# 必须重写父类的抽象方法
四、多态
4.1、多态
对象的多种状态——父类对象的多种(子类对象)的状态
为何要用多态?
多态性:在多态的背景下,可以在不用考虑对象具体类型的前提下而直接使用对象
多态性的精髓:统一
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
import abc
class People(metaclass=abc.ABCMeta):
def __init__(self, name):
self.name = name
@abc.abstractmethod
def speak(self):
pass
class Chinese(People):
def speak(self):
print("说中国话")
class England(People):
def speak(self):
print("说英语")
if __name__ == '__main__':
# 多态的体现:功能或是需求,需要父类的对象,可以传入父类对象或任意子类对象均可以
# 注:一般都是规定需要父类对象,传入子类对象
def ask_someone(obj):
print('让%s上台演讲' % obj.name) # 父类提供,自己直接继承
obj.speak() # 父类提供,子类重写
ch = Chinese('王大锤')
en = England('TOM')
ask_someone(ch)
ask_someone(en)
4.2、鸭子类型
# -*- coding: utf-8 -*-
# @Author : Sunhaojie
# @Time : 2019/4/22 15:33
import abc
class People(metaclass=abc.ABCMeta):
def __init__(self, name):
self.name = name
@abc.abstractmethod
def speak(self): pass
class Chinese(People):
def speak(self):
print('说中国话')
class England(People):
def speak(self):
print('说英国话')
# 鸭子类型:
# 1、规定有什么属性和有什么方法的类的类型叫鸭子类型
# 2、能提供出规定的属性与方法的对象就是鸭子
class Duck:
def __init__(self, name):
self.name = name
def speak(self):
print('说鸟语')
if __name__ == '__main__':
def ask_someone(obj):
print('让%s上台演讲' % obj.name)
obj.speak()
test = Duck('鸭子')
ask_someone(test)
五、内置方法
满足某种条件时触发,完成一些定制的事情
5.1、__call__:在对象被调用时自动触发该方法。
class Foo:
def __init__(self,x,y):
self.x = x
self.y = y
obj = Foo(1,2)
obj() -- 对象加括号调用报错:Foo is not callable
--改成以下代码可以给对象加括号调用
class Foo:
def __init__(self,x,y):
self.x = x
self.y = y
def __call__(self,*args,**kwargs):
print(self,*args,**kwargs)
obj = Foo(1,2)
obj(1,2,a=3,b=4) -- 加了call方法就不报错
5.2、__str__和__del__方法
class A:
def __init__(self, name, age):
self.name = name
self.age = age
格式化方法:在外界打印该类对象时被调用
可以用来定义对象被打印的输出信息
def __str__(self):
# return 'abc' 外界打印A类的对象,都打印 字符串 abc
# return super().__str__() 系统默认的在父类中返回的是对象存放的地址信息
return '<name:%s | age:%s>' % (self.name, self.age) 根据对象实际的属性格式化具体的输出内容
析构方法:在对象被删除时先自动触发,可以用来回收对象以外的其他相关资源,比如系统资源
def __del__(self):
# del会在self代表的对象被消耗的时候被调用
# 我们可以在析构函数中释放该对象持有的其他资源,
# 或者将一些持有资源持久化(保存到文件或数据库中)
del self.name # 也可以将name存起来
a = A('老王', 88)
print(a, type(a))
import time
time.sleep(5)
print('文件马上执行完毕,a就会被销毁')
print('-------------------------------------------------')
class Foo:
def __del__(self):
print('run...')
obj = Foo()
print('主-------') --程序打印完'主-------'之后结束,触发析构函数的运行,再打印'run...'。
如果在print('主-------')之前加del obj,则立即触发析构函数,即先打印'run...',再打印'主-------'
5.3、了解:对象.语法的内部实现
class B:
# 了解:对象.语法的内部实现
def __setattr__(self, key, value):
# print(key, value)
b.__dict__[key] = value
# b.__dict__[key] = value.lower()
# b.__dict__['xyz'] = 'XYZ'
# 了了解:将对象添加属性的方式可以通过字典形式
def __setitem__(self, key, value):
self.__dict__[key] = value
b = B()
b.name = 'BBB' # b.__dict__['name'] = 'BBB'
print(b.name)
b['age'] = 18
print(b.age) # 访问还是通过.语法访问
六、反射
概念:通过字符串来反射/映射到对象/类的属性上
之前操作属性,都是通过.语法完成的增删改查,本质是去对象或类的名称空间,以 对象.__dict__['key']=的方式完成对属性的操作。而反射是用字符串的形式反射到属性。
hasattr(obj, 'name') --本质是去对象的__dict__,判断'name'是否存在
getattr(obj, 'name', None) --查询一个字符串是否存在。第三个参数是默认值,一般给None,避免报错
setattr(obj,'name','EGON') --本质:obj.__dict['name']='EGON'。存在即修改,不存在就添加
delattr(obj, 'name') --删除属性
例:
import os
print(hasattr(os,'remove'))
--案例:
class Ftp:
def get(self):
print('get')
def put(self):
print('put')
def login(self):
print('login')
def run(self):
while True:
cmd=input('>>>: ').strip() #cmd='get'
if hasattr(self,cmd):
method=getattr(self,cmd)
method()
else:
print('输入的方法不存在')
obj=Ftp()
obj.run()
总结:
类的属性用类来操作
对象的属性用对象来操作
方法建议使用类来操作,得到的方法调用时
-- 对象的方法要传入具体的对象
-- 类的方法不需要传入参数
class A:
num = 10
print(hasattr(A, 'num'))
res = getattr(A, 'num', '默认值')
print(res)
delattr(A, 'num')
print(setattr(A, 'tag', 10))
# 类的属性类来操作
class B:
def __init__(self, name):
self.name = name
b = B('shj')
print(hasattr(b, 'name'))
print(getattr(b, 'name', '对象的属性类不能获取'))
delattr(b, 'name')
print(setattr(b, 'age', 18))
# 对象的属性对象来操作
class C:
def fn(self):
print('fn')
@classmethod
def func(cls):
print('func')
fn = getattr(C, 'fn')
c = C()
fn(c) # 类获取对象方法调用时传入具体的对象
obj_fn = getattr(c, 'fn')
obj_fn() # 对象获取对象方法调用时不用传参
func = getattr(C, 'func')
func() # 类获取类方法调用时不需要传入参数
七、异常处理
7.1、基本
异常:程序运行时的错误
程序中的异常处理机制:
1.程序中的所有异常都会被处理
2.程序中的所有异常都需要手动处理
3.如果没有手动处理异常,异常会交给Python解释器处理
-- 处理的方式就是打印异常信息,并停止解释器
异常信息的三部分:
1.异常的追踪信息:提示错误位置
2.异常的类型:告知处理异常应该捕获什么类型
3.异常的内容:告知错误信息
处理异常的语法:
try:
--会出现异常的代码块
except 异常类型 as 异常别名:
--异常处理逻辑
else:
--没有出现异常会执行该分支
finally:
--无论是否出现异常都会执行该分支
'''
try:
print(adsdasadsdasadsdas)
except NameError as e:
print('异常信息:', e)
else:
print('被检测的代码块正常执行该分支')
finally:
print('异常是否出现都会执行该分支')
常见的异常:
| 异常名称 | 描述 |
| ------------------------- | -------------------------------------------------- |
| | |
| BaseException | 所有异常的基类 |
| SystemExit | 解释器请求退出 |
| KeyboardInterrupt | 用户中断执行(通常是输入^C) |
| Exception | 常规错误的基类 |
| StopIteration | 迭代器没有更多的值 |
| GeneratorExit | 生成器(generator)发生异常来通知退出 |
| StandardError | 所有的内建标准异常的基类 |
| ArithmeticError | 所有数值计算错误的基类 |
| FloatingPointError | 浮点计算错误 |
| OverflowError | 数值运算超出最大限制 |
| ZeroDivisionError | 除(或取模)零 (所有数据类型) |
| AssertionError | 断言语句失败 |
| AttributeError | 对象没有这个属性 |
| EOFError | 没有内建输入,到达EOF 标记 |
| EnvironmentError | 操作系统错误的基类 |
| IOError | 输入/输出操作失败 |
| OSError | 操作系统错误 |
| WindowsError | 系统调用失败 |
| ImportError | 导入模块/对象失败 |
| LookupError | 无效数据查询的基类 |
| IndexError | 序列中没有此索引(index) |
| KeyError | 映射中没有这个键 |
| MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
| NameError | 未声明/初始化对象 (没有属性) |
| UnboundLocalError | 访问未初始化的本地变量 |
| ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
| RuntimeError | 一般的运行时错误 |
| NotImplementedError | 尚未实现的方法 |
| SyntaxError | Python 语法错误 |
| IndentationError | 缩进错误 |
| TabError | Tab 和空格混用 |
| SystemError | 一般的解释器系统错误 |
| TypeError | 对类型无效的操作 |
| ValueError | 传入无效的参数 |
| UnicodeError | Unicode 相关的错误 |
| UnicodeDecodeError | Unicode 解码时的错误 |
| UnicodeEncodeError | Unicode 编码时错误 |
| UnicodeTranslateError | Unicode 转换时错误 |
| Warning | 警告的基类 |
| DeprecationWarning | 关于被弃用的特征的警告 |
| FutureWarning | 关于构造将来语义会有改变的警告 |
| OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
| PendingDeprecationWarning | 关于特性将会被废弃的警告 |
| RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
| SyntaxWarning | 可疑的语法的警告 |
| UserWarning | 用户代码生成的警告 |
7.2、捕获异常的语法
print('start')
# 异常语法
'''
try:
# 会出现异常的代码块
except 异常类型 as 异常别名:
# 异常处理逻辑
else:
# 没有出现异常会执行该分支
finally:
# 无论是否出现异常都会执行该分支
'''
# 将可能出现异常的代码放在try分支进行检测
# -- 如果不出现异常,正常执行内部所有代码
# -- 如果出现异常会进入except分支
# part1
# 1.建议大家对异常处理时,一次只处理一个异常
try:
print(asdsdsdsdsdsdsdsdsdsdsdsdsd) # NameError
except NameError: # except 后跟异常类型,如果不需要查看异常信息,可以省略异常信息
print('出现了NameError异常')
try:
ls = [1, 2, 3, 4, 5]
print(ls[10]) # IndexError
except IndexError as e: # 如果想知道异常信息,用别名接收
print('出现了IndexError异常: %s' % e)
# part2
# 2.如果无法避免一句话或是一个完整的代码结构会出现多个可能的异常,需要在一个try中提供多个except
# ls = [1, 2, 3, 4, 5]
ls = (1, 2, 3, 4, 5, 6)
# try:
# print(ls[5]) # IndexError
# ls.append(10) # AttributeError
# except IndexError as e:
# print('出现了IndexError异常: %s' % e)
# except AttributeError as e:
# print('出现了AttributeError异常: %s' % e)
try:
print(ls[5]) # IndexError
ls.append(10) # AttributeError
except (AttributeError, IndexError) as e:
print('出现了异常: %s' % e)
print('===============================')
# part3
# 3.有些异常提前无法明确,或是压根没有明确的必要,可以捕获异常的父类异常
ls = [1, 2, 3, 4, 5]
# ls = (1, 2, 3, 4, 5, 6)
try:
print(ls[5]) # IndexError
ls.append(10) # AttributeError
except Exception as e: # 可以通过多态的应用,捕获父类,只要抛出的是该父类的子类异常,均可以被捕获
print('出现了异常: %s' % e)
# BaseException:所有异常的基类 | Exception:常规错误的基类
# part4
# 4.了了解 - try语法的else分支:当try检测的代码块没有出现异常,才会走else分支
try:
print(aaaa)
except Exception as e:
print('出现了异常', e)
else:
print('没有异常')
# part5
# 5.finally:无论是否出现异常都会执行该分支
try:
f = open('1.txt', 'w', encoding='utf-8')
f.write(b'123')
except Exception as e:
print('出现了异常', e)
finally:
print('无论是否出现异常都会执行该分支')
f.close() # 文件只要打开,不管操作是否出现异常,都需要释放文件资源
print('end')
7.3、主动抛异常
# raise ValueError('输入的不能被转换为int类型')
def get_int():
num = input('num: ')
try:
return int(num)
except ValueError:
raise ValueError('输入的不能被转换为int类型')
num = get_int()
print(num)
7.4、自定义异常
# 自定义异常的目的:想抛一个有意义的异常,但这个异常系统没有提供,自定义一个
class PeopleNameError(Exception):
# pass
# 可以通过__init__明确外界的错误信息接收给那个属性
# 再在__str__中格式化外界捕获异常,打印异常信息的格式
def __init__(self, msg):
self.msg = msg
def __str__(self):
return 'PeopleNameError: ' + self.msg
def get_name():
name = input('name: ')
if 'sb' in name.lower():
raise PeopleNameError('人名不能有敏感词汇')
return name
try:
print(get_name())
except PeopleNameError as e:
print(e) # PeopleNameError: 人名不能有敏感词汇
7.5、断言
'''
num = int(input('num: '))
if num > 0:
raise Exception('num为正值,求绝对值没有意义')
print(abs(num))
'''
num = int(input('num: '))
assert num < 0 --断言:只有满足断言条件,程序才能往下执行,反之抛出异常
print(abs(num))