面向对象与元类

面对面对象

前戏

'第一步:直接手写英雄和反派的字典'
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__方法专门用于给对象添加属性
posted @ 2022-07-31 14:42  无言以对啊  阅读(74)  评论(0)    收藏  举报