zoukankan      html  css  js  c++  java
  • python面向对象的多态-类相关内置函数-类内置魔法函数-迭代器协议-上下文管理-04

    多态

    一种事物具备不同的形态

    例如:水 --> 固态、液态、气态

    多态:# 多个不同对象可以相应同一个对象,产生不同的结果

    首先强调,多态不是一种特殊的语法,而是一种状态,特性(多个不同对象可以相应同一个方法,长身不同的结果)

    好处:对于使用者而言,使用成本降低

    ​ 之前的USB接口下的鼠标,键盘,就属于多态

    接口抽象类 鸭子类型都可以写出具备多态的代码(最简单的就是鸭子类型)

    '''
    要管理 鸡 鸭 鹅
        如何能够最方便的管理,就是我说同一句话,他们都能理解
        他们拥有相同的方法
    '''
    
    
    class Chicken:
        @staticmethod
        def bark():
            print("咯咯咯咯")
    
        @staticmethod
        def spawn():
            print("下鸡蛋...")
    
    
    class Duck:
        @staticmethod
        def bark():
            print("嘎嘎嘎")
    
        @staticmethod
        def spawn():
            print("下鸭蛋...")
    
    
    class E:
        @staticmethod
        def bark():
            print("鹅鹅鹅鹅")
    
        @staticmethod
        def spawn():
            print("下鹅蛋...")
    
    
    j = Chicken()
    y = Duck()
    e = E()
    
    
    def mange(obj):
        obj.spawn()
    
    
    mange(j)
    # 下鸡蛋...
    mange(y)
    # 下鸭蛋...
    mange(e)
    # 下鹅蛋...
    

    python中常见的多态(不同的对象类型,拥有相同的方法,不同的结果)

    # 不管什么类型,他都与type这个方法  ---> python中多态的体现
    # 多态在python中其实很常见,因为到处充斥着继承与组合
    a = 10
    b = '10'
    c = [10]
    
    print(type(a))
    print(type(b))
    print(type(c))
    # <class 'int'>
    # <class 'str'>
    # <class 'list'>
    

    常见的内置函数

    • isinstance
     # isinstance()  # 判断一个对象是不是某个类的实例
     # 参数1 要判断的对象,参数2 要判断的类型 
    def add_num(a, b):
        # if type(a) == type(b):
        if isinstance(a, int) == isinstance(b, int):
            return a+b
        else:
            print("数据类型不符")
    
    
    add_num("100", 10)
    
    • issubclass
    # issubclass() # 判断一个类是不是另一个类的子类
    #   参数一:子类,参数二:父类
    class Animal:
        @staticmethod
        def eat():
            print("动物得吃东西...")
    
    
    class Pig(Animal):
        @staticmethod
        def eat():
            print("猪吃东西...")
    
    
    class Tree:
        @staticmethod
        def light():
            print("植物光合作用...")
    
    
    def mange(obj):
        # if isinstance(obj, Animal):
        if issubclass(type(obj), Animal):
            obj.eat()
        else:
            print("不是动物...")
    
    
    pig = Pig()
    t = Tree
    mange(pig)
    # 猪吃东西...
    mange(Tree)  # AttributeError: type object 'Tree' has no attribute 'eat'
    # 不是动物...
    

    面向对象的内置魔法函数

    • __str__
    '''
        __str__ 会在对象被转为字符串时,转换的结果就是这个函数的返回值
        使用场景:我们可以利用该函数来自定义,对象是打印格式
    '''
    class Person:
        def __str__(self):  # 重写object中的 __str__
            print("__str__ run")
            return 'abc'  # abc下面的报错那里就变成了 abc
    
    
    p = Person()
    # 所有的类都可以转成字符串
    print(p)  # 打印了 __str__ run,又报错了
    # __str__ run
    # abc  # 写return 之前TypeError: __str__ returned non-string (type NoneType)  --> __str__ 必须要有一个str类型的返回值
    
    str(p)  # 没有写print 在控制台也输出了 __str__ run
    # __str__ run
    

    将对象以指定格式输出

    # print打印对象时内存地址,没什么意义,此时就可以利用__str__来自定义对象打印
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __str__(self):  # 重写object中的 __str__
            return f"这是要给Person对象,name:{self.name},age:{self.age}"
    
    
    p = Person('jack', 10)
    # 所有的类都可以转成字符串
    print(p)  # 打印了
    # 这是要给Person对象,name:jack,age:10
    
    • __del__
    # del 析构函数   (__init__ 构造函数)
    # 执行时机:手动删除对象时立马执行,或是程序运行结束时也会自动执行(垃圾回收机制?)
    # 使用场景:当你的对象再使用过程中打开了不属于解释器的资源,例如文件,网络端口
    import time
    
    
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __del__(self):  # 重写object中的 __str__
            print("del run...")
            return "del run"
    
    
    p = Person("jack", 20)
    # del p  # 删除对象触发 __del__函数执行
    # # del run...
    time.sleep(2)
    print("over")
    # over
    # del run...  # 程序结束后会把名称空间清除掉,清除时触发了 __del__,打印出 del run...
    

    结束使用自动关闭文件资源案例

    class FileTool:
        # 该类用于简化文件的读写操作
    
        def __init__(self, path):
            self.file = open(path, 'rt', encoding='utf-8')
    
        def read(self):
            return self.file.read()  # rt模式不推荐直接读字节(汉字、英文字节不同),可以一行一行读
    
        # 执行这个函数可以确定一个函数,这个对象肯定不用了,所以就可以放心的关心文件了
        def __del__(self):
            self.file.close()
    
    
    tool = FileTool("a.txt")
    print(tool.read())  # 文件属于操作系统,不受垃圾回收机制管理
    # aaaaaaaaaaaa
    
    # 不知道什么不使用该对象,那就写在 __del__函数中,当其被删除时,指定关闭资源
    
    • __call__
    # call  调用对象时自动执行
    # 执行时机:在调用对象时自动执行 ---> 对象()
    
    
    class A:
        # 调用对象时自动执行
        def __call__(self, *args, **kwargs):
            print("__call__ run...")
            print(args)
            print(kwargs)
    
    
    a = A()
    
    a(1, 2, a=100, c=300)  # 对象加括号调用
    # __call__ run...
    # (1, 2)
    # {'a': 100, 'c': 300}
    
    • __slots__

    python是动态语言,可以在运行期间动态修改对象的属性,如何能存储更多属性呢?
    需要开启更大的内存区域,将原始的属性赋值过去
    问题:如果开启的容量太大(为了效率牺牲了空间),将造成内存的浪费
    解决方案:在创建对象是告诉系统这个对象只有哪些属性,也就是固定了对象的属性数量,这样就可任意要多少开多少,减少空间浪费(使用__slots__

    import sys
    
    
    class Person:
        __slots__ = ['name']  # 加了以后再添加属性就不行了,限制属性
    
        # def __init__(self, name, age):
        def __init__(self, name):
            self.name = name
            # self.age = age  # 未在__slots__中声明,直接报错 AttributeError: 'Person' object has no attribute 'age'
    
    
    # p = Person("jck", 18)
    p = Person("jck")
    
    print(sys.getsizeof(p))  # 获取对象占用内存大小
    # 56 ---> 48  ---> __slots__ 指定有哪些属性,从而节省了内存空间(没指定__slots__之前56,指定之后48)
    
    # print(p.__dict__)  # 报错,可变字典也被省掉了(名称空间连开都不开了),AttributeError: 'Person' object has no attribute '__dict__'
    

    该属性是一个类属性,用于优化对象内存

    优化的原理:将原本不固定的属性数量,变得固定了,这样的解释器就不会以这个对象创建名称空间(所以__dict__也没了),从而达到减少内存开销的效果

    另外当类中出现了__slots__时将导致这个类的对象不再添加__slots__定义之外的属性

    • __getattr__ __setattr__ __delattr__ 及点语法原理
    __getattr__ 用 .访问属性时,如果属性不存在,执行
    __setattr__ 用 .设置属性时执行
    __delattr__ 用del 对象.属性 删除属性时,执行
    
    这几个函数反映了 python解释器是如何实现 . 语法的原理
    
    __getattribute__ 该函数也是用来获取属性
    在获取属性时如果存在__getattribute__则先执行该函数,如果没有拿到属性则继续调用__getattr__函数,如果拿到了则直接返回
    
    class A:
        def __getattr__(self, item):
            print("__getattr__")
            return self.__dict__.get(item)
    
        def __setattr__(self, key, value):
            super().__setattr__(key, value)  # 这个不写将导致赋值不成功,得到None
            print('__setattr__')
    
        def __delattr__(self, item):
            print('__delattr__')
            print(item)
            self.__dict__.pop(item)
    
    
    a = A()
    a.name = 'jack'
    # __setattr__
    print(a.name)  # 这个属性存在,就没有调用 __getattr__
    # jack
    
    
    b = A()
    b.__dict__["name"] = 'jackson'  # 通过操作__dict__ 也可以操作属性(. 语法的背后就是操作 __dict__)
    print(b.name)  # 这个属性存在,就没有调用 __getattr__
    # jackson
    
    del b.name  # 触发 __delattr__
    # __delattr__
    # name
    
    print(b.name)  # b没有name这个属性了,就触发了 __getattr__
    # __getattr__
    # None  # b没有name这个属性了
    
    class B:
        def __setattr__(self, key, value):  # 利用了 .语法赋值改值就会触发这个函数
            self.__dict__[key] = value
            print(f"{key}:{value}")
    
    
    b = B()
    b.name = 'jerry'
    # name:jerry
    b.name = 'tom'
    # name:tom
    print(b.name)
    # tom
    
    b.__dict__['halo'] = 'hi'  # 直接通过操作 __dict__ 也可以完成属性的增改
    print(b.halo)
    # hi
    
    • []的实现原理(__getitem__ __setitem__ __delitem__

    任何的符号,都会被解释器解释称特殊含义,例如 . [] ()

    __getitem__ 当你用中括号去获取属性时 执行
    __setitem__ 当你用中括号去设置属性时 执行
    __detitem__ 当你用中括号去删除属性时 执行
    
    
    '''
    需求:
        让一个对象支持 点语法来取值,也支持括号取值
    '''
    
    
    class MyDict(dict):
        def __getattr__(self, key):
            return self.get(key)
            # return self[key]  # KeyError: 'name'
    
        def __setattr__(self, key, value):
            self[key] = value
    
        def __delattr__(self, item):
            del self[item]
    
    
    # 继承 dict 可以直接用字典的一些方式
    a = MyDict()
    a['name'] = 'jack'
    print(a['name'])
    # jack
    
    # 使用 .语法(通过实现__getattr__ 、__setattr__、__delattr__来实现)
    a.name = 'sum'
    print(a.name, a['name'])
    # sum sum
    print(a['name'])
    # sum
    a.name = 'jackson'
    print(a.name)
    # jackson
    del a.name
    print(a.name)
    # None  # 用的是 .get 所以不会报错
    
    • > >= == != < <= 等比较运算符的的实现原理(运算符重载)(__gt__ __ge__ __eq__ __ne__ __lt__ __le__)

    当我们在使用某个符号时,python解释器都会为这个符号定义一个含义,同时调用对应的处理函数,当我们需要自定义对象的比较规则时,就可以在子类中覆盖大于等于等的方法

    案例

    # 自定义对象的比较
    # 对象直接无法直接比较大小
    
    
    class Person:
        def __init__(self, name, height, age):
            self.name = name
            self.height = height
            self.age = age
    
    
    p1 = Person('jason', 185, 18)
    p2 = Person('tank', 179, 18)
    # print(p1 > p2)  # TypeError: '>' not supported between instances of 'Person' and 'Person'
    
    
    class Student:
        def __init__(self, name, height, age):
            self.name = name
            self.height = height
            self.age = age
    
        # 自定义比较规则
        def __gt__(self, other):
            print(self)
            print(other)
            print("__gt__")
    
            # 比身高
            # if self.height > other.height:
            #     return True
            return self.height > other.height
            # 没有返回值默认返回 None 即 False
    
        def __eq__(self, other):
            print("eq------")
            return self.name == other.name
    
    
    stu1 = Student("jack", 180, 28)
    stu2 = Student("rose", 165, 27)
    
    print(stu1 > stu2)  # 直接报错,TypeError: '>' not supported between instances of 'Student' and 'Student'
    # <__main__.Student object at 0x000001992C7C8F60>
    # <__main__.Student object at 0x000001992C7C8F98>
    # __gt__
    # True
    print(stu1 < stu2)  # 大于和小于只要实现一个即可,符号如果不同解释器会自动交换两个对象的位置
    # <__main__.Student object at 0x000001992C7C8F98>
    # <__main__.Student object at 0x000001992C7C8F60>
    # __gt__
    # False
    print(stu1)
    # <__main__.Student object at 0x000001992C7C8F60>
    print(stu2)
    # <__main__.Student object at 0x000001992C7C8F98>
    

    原本自定义对象无法直接使用大于小于来进行比较,我们可以自定义运算符来实现,让自定义对象也支持比较符

    上述代码中.other指的是另一个参与比较的对象

    大于和小于只要实现一个即可,符号如果不同解释器会自动交换两个对象的位置

    迭代器协议

    迭代器:是指具有__iter____next__的对象

    我们可以为对象增加这两个方法来让对象变成迭代器

    class MyIter:
        # num 传入,用来指定迭代次数
        def __init__(self, num):
            self.num = num
            self.c = 0
    
        def __iter__(self):
            return self
    
        def __next__(self):
            self.c += 1
            if self.c <= self.num:
                return "hahha"
            raise StopIteration  # 抛出异常
    
    
    for i in MyIter(3):
        print(i)
    # hahha
    # hahha
    # hahha
    

    自定义range函数

    class MyRange:
        def __init__(self, start, end, step=1):
            self.start = start - 1
            self.end = end
            self.step = step
    
        def __iter__(self):
            return self
    
        def __next__(self):
            self.start += self.step
            if self.start < self.end:
                return self.start
            raise StopIteration
    
    
    for i in MyRange(1, 3):
        print(i)
    # 1
    # 2
    

    上下文管理

    上下文:这个概念属于语言学科,指的是一段话的意义,要参考当前的场景,即上下文

    在python中,上下文可以理解为一个代码区间,一个范围,例如with open 打开的文件仅在这个上下文中有效

    上下文涉及到的两个方法

    • __enter__:表示进入上下文(进入某个场景了)

    • __exit__:表示退出上下文(离开了某个场景了)

    案例

    class MyOpen:
    
        def __enter__(self):
            print("enter....")
    
        def __exit__(self, exc_type, exc_val, exc_tb):  # exc --> exception
            print("exit.....")
            print(exc_type, exc_val, exc_tb)
    
    
    with MyOpen() as m:
        print("start...")
        # 1 + '123'
    # enter....
    # exit.....
    # None None None
    

    实现了上面的两个方法就可以配合with语句用了,当执行with语句时,会先执行__enter__,当代码执行完毕后执行__exit__,或者代码遇到了异常会立即执行__exit__,并传入错误信息,包含错误的类型,错误的信息,错误的追踪信息

    class MyOpen:
    
        def __enter__(self):
            print("enter....")
    
        def __exit__(self, exc_type, exc_val, exc_tb):  # exc --> exception
            print("exit.....")
            print(exc_type, exc_val, exc_tb)
            return True  # return True 可以让程序不报错
    
    
    with MyOpen() as m:
        print("start...")
        1 + '123'  # TypeError: unsupported operand type(s) for +: 'int' and 'str'
    # enter....
    # exit.....
    # None None None  # 没有报错时打印这个
    # <class 'TypeError'> unsupported operand type(s) for +: 'int' and 'str' <traceback object at 0x00000283F3EE0608>  # 有错时打印这个,若__exit__ 返回为True则控制台不报错,否则控制台也会报错
    

    注意点

    __enter__ 函数应该返回对象自己
    __exit__ 函数可以有返回值,是一个bool类型,用于表示异常是否被处理,仅在上下文中出现异常时有用
    如果为True 则意味着,异常已经被处理了
    	False 异常未被处理,程序将中断报错
    
  • 相关阅读:
    无编译/无服务器,实现浏览器的 CommonJS 模块化
    程序员如何在工作中自我增值
    软件架构被高估,清晰简单的设计被低估
    为什么会产生微服务架构?
    版本管理Git和SVN的介绍及其优缺点
    13.递归第一次
    12.二叉树的序遍历
    12.二叉树的序遍历
    10.十进制转m进制
    10.十进制转m进制
  • 原文地址:https://www.cnblogs.com/suwanbin/p/11266025.html
Copyright © 2011-2022 走看看