7.29 多态 魔法函数 上下文管理

多态


一种事物具备多种不同形态

水 固态 液态 气态

大黄蜂 汽车人 汽车

官方解释:多个不同类的对象可以响应同一个方法,产生不同的结果

首先强调多态不是一种特殊的语法,而是一种状态,特性(即多个不同对象可以响应同一个方法,产生不同的结果

多个对象有相同的使用方法

好处:对于使用者而言,大大的降低了使用难度

我们之前写的USB接口,下的鼠标、键盘就属于多态

实现多态的手段:鸭子类型(最简单),继承:接口;抽象类

'''
要管理 鸡 鸭 鹅
如何最方便的管理:
说同一句话,他们都能理解
他们拥有相同的方法
'''
# 下面这个也算鸭子?类型

class chicken:
    def bark(self):
        print('gege')
    def spawn(self):
        print('下鸡蛋')

class duck:
    def bark(self):
        print('yaay')
    def spawn(self):
        print('下鸭蛋')

class E:
    def bark(self):
        print('eeee')
    def spawn(self):
        print('下e蛋')

chicken = chicken()  # 创建对象
duck = duck()
E = E()

def manage(obj):
    obj.spawn()

manage(chicken)  # 下鸡蛋
manage(duck)  # 下鸭蛋
manage(E)  # 下e蛋

补充两个函数


isinstance

判断一个对象是否是某个类的实例

参数1 要判断的对象(a)

参数2 要判断的类型(int)

def add_num(a,b):

    # if type(a) == int and type(b) == int:
    #     return a+b  
    # 这是我们原来的判定方法

    if isinstance(a,int) and isinstance(b,int):
        return  a+b
    # 这是使用isinstance的写法


print(add_num(5,15))  # 20
print(add_num(2,'6'))  # None

issubclass

判断一个类是不个类的子类

参数1 子类

参数2 父类

class Animal:
    def eat(self):
        print('动物得吃东西')

class Tree:
    def light(self):
        print('树要光合作用')

class Pig(Animal):
    def eat(self):
        print("猪得吃东西")

pig = Pig()
t = Tree()

###############传统方法######################
def manage(isanimal):
    if type(isanimal) == Animal:
    # 如果这么写了连pig的类都管不了,因为Pig是继承于Animal的
    # 而不是等于Animal,所以并不会执行下面的语句
        isanimal.eat()
    else:
        print('不是动物')

manage(pig)  # 不是动物
# manage(t)  # 报错说没有eat方法

# 所以就需要 subclass 判断是不是子类

###############subclass方法#################
def manage(obj):
    if issubclass(type(obj),Animal):
        obj.eat()
    else:
        print('不是动物')

manage(pig)  # 猪得吃东西
manage(t)  # 不是动物
print(issubclass(Pig,object))
# True
# 需要注意的是,所有的类都直接或者间接地继承object,所以这样判断没有意义

魔法函数


双下str

双下方法会在某个时刻自动执行

传(self)这种是对象方法

__str__  会在对象被转换为字符串时执行,转换的结果就是这个函数的返回值 
使用场景:我们可以利用该函数来 自定义对象的打印格式
class Person:  # 其实这里等价于 class Person(object):
               # 即继承了object这个总的父类
               # 所以下面的__str__相当于被覆写了

    def __str__(self):
        print('run')
        return 'abc'

p = Person()
print(p)
# run
# abc

# 如果把print(p)改成下面这个
str(p)
# run # 所以说明__str__ 会在对象被转换为字符串时执行
# 转换的结果就是这个函数的返回值
class Person:  # 其实这里等价于 class Person(object):
               # 即继承了object这个总的父类

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

p = Person('jack',18)
print(p)  # <__main__.Person object at 0x1052412b0>
# 打印出来是对象的内存地址

# 如果想要的是让他打印出可用信息,而不是内存地址,

就按照下面的写(其实就是用双下str方法换一下要return的值)

class Person:  # 其实这里等价于 class Person(object):
               # 即继承了object这个总的父类
               # 所以下面的__str__相当于被覆写了
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __str__(self):
        return '这是一个person对象,name:%s,age:%s' %(self.name,self.age)

p = Person('jack',18)
print(p)  # 这是一个person对象,name:jack,age:18

双下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__

img

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 当你用中括号去设置属性时 执行
delitem 当你用中括号去删除属性时 执行

'''
需求:
    让一个对象支持 点语法来取值,也支持括号取值
'''


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 异常未被处理,程序将中断报错

转载于:https://www.cnblogs.com/PowerTips/p/11264371.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值