面对面对象
前戏
'第一步:直接手写英雄和反派的字典'
hero1 = {'name': '闪电侠',
'ability': '神速力',
'attack': 500,
'hit_points': 800}
hero2 = {'name': '蝙蝠侠',
'ability': '钱',
'attack': 100,
'hit_points': 300}
villain1 = {'name': '逆闪电',
'ability': '逆神速力',
'attack': 300,
'hit_points': 500}
villain2 = {'name': '小丑',
'ability': '笑气',
'attack': 100,
'hit_points': 100}
'第二步:定义英雄和反派的字典基本不变且要反复使用,所以用函数包起来'
def func_hero(name, ability, attack, hit_points):
'''
产生英雄的字典
:param name: 名字
:param ability: 能力
:param attack: 攻击力
:param hit_points: 生命值
:return: 英雄的字典
'''
hero_obl = {
'name': name,
'ability': ability,
'attack': attack,
'hit_points': hit_points}
return hero_obl
def func_villain(name, ability, attack, hit_points):
'''
产生反派的字典
:param name: 名字
:param ability: 能力
:param attack: 攻击力
:param hit_points: 生命值
:return: 反派的字典
'''
villain_obl = {
'name': name,
'ability': ability,
'attack': attack,
'hit_points': hit_points}
return villain_obl
h1 = func_hero('闪电侠', '神速力', 500, 800)
v1 = func_villain('逆闪电侠', '逆神速力', 300, 500)
print(h1)
# {'name': '闪电侠', 'ability': '神速力', 'attack': 500, 'hit_points': 800}
print(v1)
# {'name': '逆闪电侠', 'ability': '逆神速力', 'attack': 300, 'hit_points': 500}
'第三步:让英雄和反派互相攻击'
def hero_attack(hero_obj, villain_obj):
'''
用于英雄攻击反派
:param hero_obj: 传英雄数据
:param villain_obj: 传反派数据
'''
print('将被攻击的反派:%s 目前生命值还有%s' % (villain_obj.get('name'), (villain_obj.get('hit_points'))))
villain_obj['hit_points'] -= hero_obj['attack']
print('英雄:%s 攻击 反派:%s 掉血:%s 剩余血量:%s' %
(hero_obj.get('name'), villain_obj.get('name'), hero_obj.get('attack'), villain_obj.get('hit_points')))
def villain_attack(hero_obj, villain_obj):
'''
用于反派攻击
:param hero_obj: 传英雄数据
:param villain_obj: 传反派数据
'''
print('将被攻击的英雄:%s 目前生命值还有%s' % (hero_obj.get('name'), hero_obj.get('hit_points')))
hero_obj['hit_points'] -= villain_obj['attack']
print('反派:%s 攻击 英雄:%s 掉血:%s 剩余血量:%s' %
(villain_obj.get('name'), hero_obj.get('name'), villain_obj.get('attack'), hero_obj.get('hit_points')))
h1 = func_hero('闪电侠', '神速力', 500, 800)
h2 = func_hero('蝙蝠侠', '钱', 100, 300)
v1 = func_villain('逆闪电侠', '逆神速力', 300, 500)
v2 = func_villain('小丑', '笑气', 100, 100)
hero_attack(h1, v1)
# 将被攻击的反派:逆闪电侠 目前生命值还有500
# 英雄:闪电侠 攻击 反派:逆闪电侠 掉血:500 剩余血量:0
villain_attack(h2, v2)
# 将被攻击的英雄:蝙蝠侠 目前生命值还有300
# 反派:小丑 攻击 英雄:蝙蝠侠 掉血:100 剩余血量:200
优化
'问题:英雄可以使用反派攻击英雄的函数,反派也可以使用英雄攻击反派的函数'
hero_attack(v1, h1)
# 将被攻击的反派:闪电侠 目前生命值还有800
# 英雄:逆闪电侠 攻击 反派:闪电侠 掉血:300 剩余血量:500
'第四步:英雄和英雄攻击反派的函数绑定,反派和反派攻击英雄的函数绑定,让函数只有特定'
def func_hero(name, ability, attack, hit_points):
'''
产生英雄的字典
:param name: 名字
:param ability: 能力
:param attack: 攻击力
:param hit_points: 生命值
:return: 英雄的字典
'''
def hero_attack(hero_obj, villain_obj):
'''
用于英雄攻击反派
:param hero_obj: 传英雄数据
:param villain_obj: 传反派数据
'''
print('将被攻击的反派:%s 目前生命值还有%s' % (villain_obj.get('name'), (villain_obj.get('hit_points'))))
villain_obj['hit_points'] -= hero_obj['attack']
print('英雄:%s 攻击 反派:%s 掉血:%s 剩余血量:%s' %
(hero_obj.get('name'), villain_obj.get('name'), hero_obj.get('attack'), villain_obj.get('hit_points')))
hero_obl = {
'name': name,
'ability': ability,
'attack': attack,
'hit_points': hit_points,
'hero_attack': hero_attack}
return hero_obl
def func_villain(name, ability, attack, hit_points):
'''
产生反派的字典
:param name: 名字
:param ability: 能力
:param attack: 攻击力
:param hit_points: 生命值
:return: 反派的字典
'''
def villain_attack(hero_obj, villain_obj):
'''
用于反派攻击
:param hero_obj: 传英雄数据
:param villain_obj: 传反派数据
'''
print('将被攻击的英雄:%s 目前生命值还有%s' % (hero_obj.get('name'), hero_obj.get('hit_points')))
hero_obj['hit_points'] -= villain_obj['attack']
print('反派:%s 攻击 英雄:%s 掉血:%s 剩余血量:%s' %
(villain_obj.get('name'), hero_obj.get('name'), villain_obj.get('attack'), hero_obj.get('hit_points')))
villain_obl = {
'name': name,
'ability': ability,
'attack': attack,
'hit_points': hit_points,
'villain_attack': villain_attack}
return villain_obl
h1 = func_hero('闪电侠', '神速力', 500, 800)
h2 = func_hero('蝙蝠侠', '钱', 100, 300)
v1 = func_villain('逆闪电侠', '逆神速力', 300, 500)
v2 = func_villain('小丑', '笑气', 100, 100)
h1.get('hero_attack')(h1, v1)
# 将被攻击的反派:逆闪电侠 目前生命值还有500
# 英雄:闪电侠 攻击 反派:逆闪电侠 掉血:500 剩余血量:0
v2.get('villain_attack')(h2, v2)
# 将被攻击的英雄:蝙蝠侠 目前生命值还有300
# 反派:小丑 攻击 英雄:蝙蝠侠 掉血:100 剩余血量:200
结论
'''
把英雄的数据与英雄的功能绑定起来,只有英雄可以调用英雄的功能
把反派的数据与反派的功能绑定起来,只有反派可以调用反派的功能
将数据和功能绑定到一起被称为:面像对象编程
本质上是将数据和对应的功能绑定到一起,使其只能互相使用
'''
编程思想
1.面向过程编程
面向过程编程是一种以过程为核心的编程思想过程就是流程的意思,面向过程编程其实就是在执行流程,也就是按照步骤依次执行,最终达到结果
eg:注册功能 登录功能
2.面向对象编程
面向对象编程其实就是一个容器,将数据和功能绑定到了一起
eg:游戏人物(只负责创造出人物以及人物具备的功能)
'''
面向过程编程相当于让你给出问题的具体解决方法
面向对象编程相当于创造事物之后的事使用管
这两种编程思想没有优劣之分,只是使用场景的不同,很多时候是两者共用
'''
对象与类
对象与类的概念
对象:数据与功能的结合体
类:多个对象相同的数据与功能的结合体
'''
eg:
对象:人 类:多个人
对象:狗 类:多个狗
'''
对象用于记录多个对象不同的数据与功能
类用于记录多个对象相同的数据与功能
对象与类的创建
1.在编程中必须要先有类才能产生对象
2.创建类的语法:
class Student:
# 学生对象公共的数据
# 学生对象公共的方法
class是定义类的关键字
Student是类名,和变量名一样,首字母最好大写,便于区分
类体代码:英雄对象公共的数据/英雄对象公共的方法
类体代码在定义阶段就会执行
3.查看名称空间的方法
class Student:
# 学生对象公共的数据
school = '霍格沃兹魔法学院'
# 学生对象公共的方法
def watch_games(self):
print('正在看魁地奇比赛')
# 可以使用字典的取值方式获取数据值
print(Student.__dict__)
print(Student.__dict__['school']) # 霍格沃兹魔法学院
# 也可以使用句点符
print(Student.school) # 霍格沃兹魔法学院
# 类名加括号
res1 = Student()
res2 = Student()
print(res1.school) # 霍格沃兹魔法学院
print(res2.school) # 霍格沃兹魔法学院
print(res1) # <__main__.Student object at 0x000001706EA83B50>
print(res2) # <__main__.Student object at 0x000001706EA83AF0>
print(res1.__dict__, res2.__dict__) # {} {}
# 修改school键对应的值
Student.school = '德姆斯特朗魔法学院'
print(res1.school) # 德姆斯特朗魔法学院
print(res2.school) # 德姆斯特朗魔法学院
对象独有的数据
class Student:
# 学生对象公共的数据
school = '霍格沃兹魔法学院'
# 学生对象公共的方法
def watch_games(self):
print('正在看魁地奇比赛')
'步骤1:用__dict__方法朝字典添加键值对'
res1 = Student()
res1.__dict__['name'] = 'harry' # res1.name = 'harry'
res1.__dict__['strong_point'] = '黑魔法防御术'
res1.__dict__['position'] = '魔法部法律执行司司长'
print(res1.name) # harry
print(res1.strong_point) # 黑魔法防御术
print(res1.position) # 魔法部法律执行司司长
res2 = Student()
res2.__dict__['name'] = 'hermione'
res2.__dict__['strong_point'] = '学习'
res2.__dict__['position'] = '魔法部部长'
print(res2.name) # hermione
print(res2.strong_point) # 学习
print(res2.position) # 魔法部部长
'步骤2:将添加独有数据的代码封装成函数'
class Student:
# 学生对象公共的数据
school = '霍格沃兹魔法学院'
# 学生对象公共的方法
def watch_games(self):
print('正在看魁地奇比赛')
def init(res, name, strong_point, position):
res.__dict__['name'] = name
res.__dict__['strong_point'] = strong_point
res.__dict__['position'] = position
res1 = Student()
res2 = Student()
init(res1, 'harry', '黑魔法防御术', '魔法部法律执行司司长')
init(res2, 'hermione', '学习', '魔法部部长')
print(res1.__dict__)
# {'name': 'harry', 'strong_point': '黑魔法防御术', 'position': '魔法部法律执行司司长'}
print(res2.__dict__)
# {'name': 'hermione', 'strong_point': '学习', 'position': '魔法部部长'}
'步骤3:init函数是用来创造独有的数据,其他对象不能调用>>>面向对象思想:将数据和功能绑定到一起'
class Student:
'''
先产生一个空对象
自动调用类里面的__init__方法,将产生的空对象当成第一个参数传入
'''
def init(res, name, strong_point, position):
res.name = name # res.__dict__['name'] = name
res.strong_point = strong_point # res.__dict__['strong_point'] = strong_point
res.position = position # res.__dict__['position'] = position
# 学生对象公共的数据
school = '霍格沃兹魔法学院'
# 学生对象公共的方法
def watch_games(self):
print('正在看魁地奇比赛')
res1 = Student()
Student.init(res1, 'harry', '黑魔法防御术', '魔法部法律执行司司长')
print(res1.__dict__)
# {'name': 'harry', 'strong_point': '黑魔法防御术', 'position': '魔法部法律执行司司长'}
print(res1.name)
# harry
对象独有的功能
class Student:
school = '霍格沃兹魔法学院'
def __init__(self, name):
self.name = name # 对象的独有的数据
def est(self): # 对象调用时会将对象当做第一个参数传入
print('%s正在看魁地奇比赛' % self.name)
res1 = Student('harry')
res1.est()
# harry正在看魁地奇比赛
res2 = Student('hermione')
res2.est()
# hermione正在看魁地奇比赛
'''
对对象独有的方法,无法实现,在全局中不是独有的,在类中则是公共的
python解释器有一个特性,定义在类中的函数是绑定给对象的,相当于对象的独有方法
'''
动静态方法
'''
在类体代码中编程的函数
1.要绑定给对象的方法,就直接在类体代码中编程,对象调用时会自动将对象当第一个参数传入,类调用时有几个形参就传几个实参
2.静态方法就只是普通的函数
'''
class Student:
school = '霍格沃兹魔法学院'
def __init__(self): # self用于接收对象
print('开始喽', self)
@classmethod # 绑定给类的方法
def est(cls): # cls用于接收对象
print('正在看魁地奇比赛', cls)
@staticmethod # 静态方法
def run(a, b): # 调用时必须按照函数的传参要求
print('溜了溜了')
res1 = Student()
# 开始喽 <__main__.Student object at 0x000002399E2EF190>
Student.est()
# 正在看魁地奇比赛 <class '__main__.Student'>
res1.est()
# 正在看魁地奇比赛 <class '__main__.Student'>
Student.run(1, 2)
# 溜了溜了
res1.run(1, 2)
# 溜了溜了
面向对象三特性
面向对象三特性之继承
'面向对象三大特性分别是:继承、封装、多态'
1.继承的含义
继承是用来描述类与类之间数据的关系,如a继承类b,a就拥有了b所有的数据与功能
2.继承的目的
在编程的数据里继承可以帮我们节省代码的编写,且一个类可以继承多个类
3.继承的操作
class 子类名(父类名):
pass
'''
1.定义类名时在类名后面加括号,括号内填写需要继承的类名,如果要继承多个类名,可以用逗号隔开
2.类的称呼
被继承的类称为:父类或基类或超类
继承的类称为:子类或派生类
'''
继承的本质
1.抽象:将多个类共同的数据或功能抽取出来形成一个父类
2.继承:一层一层的获取父类里的资源
'''
对象:数据和功能的结合体
类:多个对象相同的数据和功能的结合体
父类:多个类相同的数据和功能的结合体
'''
名字的查找顺序
1.不继承的情况下查找名字的顺序
class Student:
school = '霍格沃兹魔法学院'
def est(self): # cls用于接收对象
print('正在看魁地奇比赛')
res = Student()
print(res.school)
# 霍格沃兹魔法学院
res.school = '德姆斯特朗魔法学院' # 对象点名字是在自身的名称空间中产生了新的school
print(res.school)
# 德姆斯特朗魔法学院
print(Student.school)
# 霍格沃兹魔法学院
'先在对象自身查找,若没有,再去产生该对象的类中查找'
2.单继承情况下名字的查找顺序
class A:
name = 'from A'
pass
class B(A):
name = 'from B'
pass
class C(B):
name = 'from C'
pass
odj = C()
# odj.name = 'from odj'
print(odj.name)
# 查找顺序:from odj>>>from C>>>from B>>>from A
'先在对象自身查找,若没有,再去产生该对象的类中查找,若还是没有,则去类的父类中查找...'
3.多继承的情况下名字的查找顺序
3.1非菱形继承
class A:
name = 'from A'
pass
class B:
name = 'from B'
pass
class C(B):
name = 'from C'
pass
class D(A):
name = 'from D'
pass
class E(D, C):
name = 'from E'
pass
odj = E()
# odj.name = 'from odj'
print(odj.name)
# 查找顺序:from odj>>>from E>>>from D>>>from A>>>from C>>>from B
'非菱形继承是深度优先,它是将类的每个分支走到底,在换下一分支'
3.2菱形继承
class F:
name = 'from F'
pass
class A(F):
name = 'from A'
pass
class B(F):
name = 'from B'
pass
class C(B):
name = 'from C'
pass
class D(A):
name = 'from D'
pass
class E(D, C):
name = 'from E'
pass
odj = E()
# odj.name = 'from odj'
print(odj.name)
# 查找顺序:from odj>>>from E>>>from D>>>from A>>>from C>>>from B>>>from F
'菱形继承是广度优先,它是走完全部分支在走最后一个父类'
print(E.mro()) # 也可以用mor获取执行顺序
# [<class '__main__.E'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.F'>, <class 'object'>]
经典类与新式类
经典类:不继承object或其子类的类
新式类:继承了object或其子类的类
'''
在python3.X中所有的类都是默认继承object,所以python3.X里只有新式类
在python2.X中有经典类和新式类
为了兼容python2.X,在定义的时候,如果没有要继承的父类,可以写成:
class 子类名(object):
pass
写代码时对object无需关心,知道它是什么就可以了
'''
派生方法
'''
调用父类可以获得父类的功能和数据,但子类要扩展新的数据和功能,就需要用到派生方法
'''
class School:
def __init__(self, name, age):
self.name = name
self.age = age
class Student(School):
def __init__(self, name, age, location):
# School.__init__(self, name, age) # 子类调用父类的方法
super().__init__(name, age) # super是专门用与子类调用父类的方法
self.location = location # 调用后在加上这个身份特有的数据
class Teacher(School):
def __init__(self, name, age, course):
# School.__init__(self, name, age)
super().__init__(name, age)
self.course = course
res1 = Student('harry', 18, '找球手')
print(res1.name) # harry
print(res1.age) # 18
print(res1.location) # 找球手
res2 = Teacher('snape', 38, '魔药学')
print(res2.name) # snape
print(res2.age) # 38
print(res2.course) # 魔药学
'派生方法可以让我们在用父类时通过super()增加功能'
class MYClasss(list):
def append(self, age):
if age == 'harry':
print('harry不能加入列表') # harry不能加入列表
return
super().append(age)
obj = MYClasss()
obj.append(110)
obj.append(120)
obj.append('harry')
obj.append(119)
print(obj) # [110, 120, 119]
派生方法的使用
import datetime
import json
t = {'t1': datetime.datetime.today(),
't2': datetime.date.today()}
res = json.dumps(t)
print(t)
'''
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type datetime is not JSON serializable
并不是所有数据类型都可以用json序列化的,上述操作就会报错:
+-------------------+---------------+
| Python | JSON |
+===================+===============+
| dict | object |
+-------------------+---------------+
| list, tuple | array |
+-------------------+---------------+
| str | string |
+-------------------+---------------+
| int, float | number |
+-------------------+---------------+
| True | true |
+-------------------+---------------+
| False | false |
+-------------------+---------------+
| None | null |
+-------------------+---------------+
'''
# 1.可以将不符合的数据类型转换为符合的数据类型
t = {'t1': str(datetime.datetime.today()),
't2': str(datetime.date.today())}
res = json.dumps(t)
print(res)
# {"t1": "2022-07-28 15:30:42.763026", "t2": "2022-07-28"}
# 2.使用派生方法
'''
按住ctrl在鼠标左键点击json可以打开底层
我们可以看到dump底层是函数
def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
default=None, sort_keys=False, **kw):
其中cls形参如果没有传参就会默认为None
if cls is None:
cls = JSONEncoder
return cls(...) # JSONEncoder()
cls为None时会给cls绑定一个JSONEncoder,查看JSONEncoder源码
class JSONEncoder(object):
发现序列化报错有default方法触发
def default(self, o):
raise TypeError(f'Object of type {o.__class__.__name__} '
f'is not JSON serializable')
default方法就是报错的结果
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type datetime is not JSON serializable
我们如果想要避免报错 那么肯定需要对default方法做修改(派生)
定义一个JSONEncoder和其内部的default方法,这样在执行时就不会走底层的报错代码,而是走我们的代码
'''
t = {'t1': datetime.datetime.today(),
't2': datetime.date.today()}
class MYJSONEncoder(json.JSONEncoder):
def default(self, o):
'o就是json即将序列化的数据'
if isinstance(o, datetime.datetime):
'如果o是datetime.datetime则返回下方'
return o.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(o, datetime.date):
'如果o是datetime.date则返回下方'
return o.strftime('%Y-%m-%d ')
'如果是可以序列化的类型,那就不做任何处理,让它序列化'
return super().default(o)
res = json.dumps(t, cls=MYJSONEncoder)
print(res)
# {"t1": "2022-07-28 15:30:03", "t2": "2022-07-28 "}
面向对象的三大特性之封装
'''
封装就是将数据或功能隐藏起来
隐藏不是为了让用户无法使用,而是给隐藏的数据开设特定的接口,让用户使用接口才可以使用,我们就可以在接口中添加一些额外操作
在类定义阶段使用双下划线开头的名字,都是隐藏的属性,后续的类和对象都无法直接获取
python不会真正限制任何代码
隐藏的属性实际上也是可以访问,只不过需要做变形处理
__变量名>>>_类名__变量名
但还是不要使用变形的名字取访问,不然就失去了隐藏的意义
'''
class Student(object):
__school = '霍格沃茨魔法大学'
'在变量名前面加__就可以将变量名隐藏'
def __init__(self, name, age):
self.__name = name
self.__age = age
res = Student('harry', 20)
print(res.__school) # 隐藏的变量名无法获取,会直接报错对象没有这个属性
print(res._Student__school)
# 霍格沃茨魔法大学
class Student(object):
__school = '霍格沃茨魔法大学'
'在变量名前面加__就可以将变量名隐藏'
def __init__(self, name, age):
self.__name = name
self.__age = age
def check(self):
'隐藏的变量名只有在类里面才可以获取'
print(f'''
学生姓名:{self.__name}
学生年龄:{self.__age}
''')
def set(self, name, age):
'隐藏的变量名也只有在类中可以更改'
if len(name) == 0:
print('名字不能为空')
return
elif not isinstance(age, int):
print('年龄只能是整数')
return
self.__name = name
self.__age = age
res = Student('harry', 20)
res.check()
# 学生姓名:harry
# 学生年龄:20
res.set('jason', 38)
res.check()
# 学生姓名:jason
# 学生年龄:38
property伪装属性
'''
可以理解为将方法伪装成数据
res.name # 数据只需要点名字
res.func() # 方法至少还要加括号
伪装之后还可以将res.func()方法伪装成res.func
'''
# 体质指数(BMI)=体重(kg)÷身高^2(m)
class Person:
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)
p1 = Person('me', 52, 1.70)
# res = p1.BMI() # 没伪装的方法使用时要加括号
res = p1.BMI # 伪装后的方法是数据,数据使用时不能加括号
print(res)
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):
'检查数据类型是不是字符串'
raise TypeError(f'{value} must be str')
'若数据类型不是字符串则报错'
self.__NAME = value
'若数据类型是字符串则执行'
@name.deleter
def name(self):
raise PermissionError('Can not delete')
'如果有代码要删除name则报错'
res = Foo('barry')
print(res.name)
# barry
res.name = 233
print(res.name)
# 报错:TypeError: 233 must be str
del res.name
# 报错:PermissionError: Can not delete
面向对象三大特性之多态
'''
多态是一种事物的多种形态,但又有相同的功能
猫是喵喵叫,狗是汪汪叫,但都是在叫
'''
class Cat:
def spark(self):
print('喵喵喵')
class Dog:
def spark(self):
print('汪汪汪')
class Duck:
def spark(self):
print('嘎嘎嘎')
res1 = Cat()
res2 = Dog()
res3 = Duck()
print(res1.spark()) # 喵喵喵
print(res2.spark()) # 汪汪汪
print(res3.spark()) # 嘎嘎嘎
'''
像这种有多种形态但相同功能的应该有相同的名字
这样无论是那种动物都可以直接调用
'''
class Animal(object):
def spark(self):
pass
class Cat(Animal):
def spark(self):
print('喵喵喵')
class Dog(Animal):
def spark(self):
print('汪汪汪')
class Duck(Animal):
def spark(self):
print('嘎嘎嘎')
res1 = Cat()
res2 = Dog()
res3 = Duck()
res1.spark() # 喵喵喵
res2.spark() # 汪汪汪
res3.spark() # 嘎嘎嘎
'python也提供了一种强制性的操作,了解即可,这应该是自觉遵守'
import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
def talk(self): # 抽象方法中无需实现具体的功能
pass
class Person(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
def talk(self):
pass
def run(self):
pass
ojd = Person()
'''
鸭子类型
只要长得像鸭子,走路像鸭子,说话像鸭子,那就是鸭子
'''
class Teacher:
def run(self):pass
def eat(self):pass
class Student:
def run(self):pass
def eat(self):pass
'有两个类没有继承任何父类,但他们有共同的功能,那我们在命名时名字就要一样'
'''
操作系统:一切都是文件
只要能读数据,能写数据,那就是文件
文件都储存在内存或硬盘里
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
class Memory: #Memory类也有两个与文件类型同名的方法:read和write
def read(self):
pass
def write(self):
pass
python:一切都是对象
只要有数据,有功能,那就是对象
文件名 文件对象
模块名 模块对象
'''
面向对象之反射
反射的使用
'''
反射是通过字符串来操作对象的数据或方法
反射主要的四个方法:
hasattr():判断对象是否含有某个字符串对应的属性
getattr():获取对象字符串对应的属性
setattr():根据字符串给对象设置属性
delattr():根据字符串给对象删除属性
'''
class Student(object):
school = '霍格沃茨魔法大学'
def choice_course(self):
print('选课')
stu = Student()
'''
判断用户提供的名字在不在对象可以使用的范围
stu.school与stu.'school'本质上是不一样的
'''
while True:
res = input('输入想要查找的名字>>>:').strip()
print(hasattr(stu, res)) # True
'hasattr可以判断否有该名字'
print(getattr(stu, res)) #霍格沃茨魔法大学
'getattr会获取名字对应的属性'
if hasattr(stu, res):
info = getattr(stu, res)
if callable(res):
print('拿到的名字是函数:', res())
else:
print('拿到的名字是数据', res)
else:
print('你要查找的名字,对象没有')
print(stu.__dict__)
# {}
stu.name = 'jason'
stu.age = 18
print(stu.__dict__)
# {'name': 'barry', 'age': 18}
setattr(stu, 'hobby', 'read')
print(stu.__dict__)
# {'name': 'jason', 'age': 18, 'hobby': 'read'}
del stu.name
print(stu.__dict__)
# {'age': 18, 'hobby': 'read'}
delattr(stu, 'age')
print(stu.__dict__)
# {'hobby': 'read'}
实战案例:
class FtpServer:
def serve_forever(self):
while True:
inp = input('input your cmd>>: ').strip()
cmd, file = inp.split()
if hasattr(self, cmd): # 根据用户输入的cmd,判断对象self有无对应的方法属性
func = getattr(self, cmd) # 根据字符串cmd,获取对象self对应的方法属性
func(file)
def get(self, file):
print('Downloading %s...' % file)
def put(self, file):
print('Uploading %s...' % file)
obj = FtpServer()
obj.serve_forever()
反射的实战
1.加载配置文件中所有纯大写的配置
import src # AA = '是大写', aa = '是小写'
new_dict = {}
print(dir(src)) # dir用于获取括号中对象可以调用的名字
# ['AA', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'aa']
for i in dir(src):
if i.isupper(): # 判断名字是不是纯大写
v = getattr(src, i)
new_dict[i] = v # 将纯大写的名字加入字典
print(new_dict)
# {'AA': '是大写'}
2.模拟操作系统cmd终端执行用户命令
class WinCmd(object):
def dir(self):
print('dir获取当前目录下所有的文件名称')
def ls(self):
print('ls获取当前路径下所有的文件名称')
def ipconfig(self):
print('ipconfig获取当前计算机的网卡信息')
obj = WinCmd()
while True:
cmd = input('输入命令>>>:').strip()
if hasattr(obj, cmd): # 判断对象是否含有字符串对应的属性
cmd_name = getattr(obj, cmd) # 获取对象字符串对应的属性
cmd_name()
else:
print(f'{cmd}不是内部或外部命令,也不是可运行的程序或批处理文件')
面向对象魔法方法
魔法方法的使用
'''
魔法方法其实就是类中定义的双下方法,之所以叫魔法方法是因为这些方法都是达到某个条件就会自动触发,也就是对象实例化
eg:__init__
'''
class MyClass(object):
def __init__(self):
'实例化对象的时候触发'
print('触发__init__')
# self.name = name
pass
def __str__(self):
'''对象被执行打印操作的时候会触发
该方法必须返回一个字符串
返回什么字符串打印对象之后就展示什么字符串'''
print('触发__str__')
return ''
def __call__(self, *args, **kwargs):
'对象加括号调用时自动触发'
print('触发__call__')
print(args)
print(kwargs)
def __getattr__(self, item):
'''对象获得一个不存在的属性名时自动触发
对象获取的不存在的属性名会获取该方法的返回值
形参item就是对象获取的不存在的属性名'''
print('触发__getattr__')
return f'属性名:{item}不存在'
def __setattr__(self, key, value):
'对象操作属性值时自动触发(对象.属性名=属性值)'
print('触发__setattr__')
def __del__(self):
'对象在被删除的时候自动触发触发'
print('触发__del__')
def __getattribute__(self, item):
'''对象获取属性的时候自动触发
无论这个属性存不存在
当类中既有__getattr__又有__getattribute__时只会走后者'''
print('触发__getattribute__')
def __enter__(self):
'对象被with语法执行时自动触发,该方法返回什么,as关键字后面的变量名就能得到什么'
print('触发__enter__')
return '对象被with语法执行'
def __exit__(self, exc_type, exc_val, exc_tb):
'对象被with语法执行并运行子代码之后自动触发'
print('触发__exit__')
res = MyClass() # 触发__init__
print(res) # 触发__str__
res(1, 2, name='barry', age=20) # 触发__call__
# (1, 2)
# {'name': 'barry', 'age': 20}
print(res.name) # 触发__getattr__
# 属性名:name不存在
res.name = 'barry' # 触发__setattr__
del res # 触发__del__
print(res.name) # 触发__getattribute__
# None
with res as f:
print(f)
# 触发__enter__
# 对象被with语法执行
# 触发__exit__
魔法方法面试题
class Context:
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
pass
with Context() as f:
f.do_something()
"""补全以上代码 执行之后不报错"""
class Context:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def do_something(self):
pass
with Context() as f:
f.do_something()
元类
元类简介
s1 = '字符串'
l1 = ['列表']
d1 = {'这是': '字典'}
print(type(s1)) # <class 'str'>
print(type(l1)) # <class 'list'>
print(type(d1)) # <class 'dict'>
'''
我们以前一直用type来查看数据类型
但其实查看的不是数据类型而是数据所属的类
我们定义的数据类型本质还是通过类产生的对象
class str:
pass
h = 'hello' str('hello')
可以理解为type用于查看当前对象的类是谁
'''
class MyClass:
pass
obj = MyClass()
print(type(obj)) # 查看产生对象obj的类:<class '__main__.MyClass'>
print(type(MyClass)) # 查看产生对象MyClass的类:<class 'type'>
"由此,我们可以得出结论,自定义的类都是由type类产生的,我们将产生类的类称为'元类'"
产生类的两种方式
1.class 关键字
class 类名:
pass
2.利用元类type
type(类名,类的父类,类的名称空间)
'利用元类type来产生类虽然更繁琐,但可以帮我们高度定制产生类的过程'
元类的使用
class MyMetaClass(type):
pass
'只有继承了type的类才可以称之为是元类'
class MyClass(metaclass=MyMetaClass):
pass
'使用关键字metaclass声明才可以切换产生类的元类'
'''
类中的__init__用于实例化对象
元类中__init__用于实例化类
'''
class MyMetaClass(type):
def __init__(self, what, bases=None, dict=None):
print('使用自定义的元类')
# 使用自定义的元类
print(what) # 类名
# MyClass
print(bases) # 类的父类
# ()
print(dict) # 类的名称空间
# {'__module__': '__main__', '__qualname__': 'MyClass'}
if not what.istitle(): # 判断类名首字母是否大写
raise Exception('首字母必须大写') # 首字母不是大写则报错
super().__init__(what, bases, dict) # 首字母大写则产生类
'只有继承了type的类才可以称之为是元类'
class Myclass(metaclass=MyMetaClass):
pass
class abc(metaclass=MyMetaClass):
pass
# 报错:Exception: 首字母必须大写
元类进阶
'元类不止可以控制类的产生,还可以控制对象的产生'
class MyMetaClass(type):
def __call__(self, *args, **kwargs):
print('触发__call__')
if args:
raise Exception('必须使用关键字传参')
super().__call__(*args, **kwargs)
class Myclass(metaclass=MyMetaClass):
def __init__(self, name, age):
self.name = name
self.age = age
print('触发__init__')
obj = Myclass('barry', 20)
# 报错:Exception: 必须使用关键字传参
obj = Myclass(name='barry', age=20)
# 触发__call__
# 触发__init__
'''
我们可以操作元类里面的__call__来定制对象的产生过程
我们可以操作元类里面的__init__来定制类的产生过程
'''
双下new方法
'''
类产生对象的步骤
1.产生一个空对象
2.自动触发__init__方法实例化对象
3.返回实例化好的对象
'''
__new__方法专门用于产生空对象
__init__方法专门用于给对象添加属性