代码改变世界

上海 day23 -- 面向对象三大特征---多态 和 内置魔法函数

2019-07-29 17:36  在上海的日子里  阅读(222)  评论(0编辑  收藏  举报

目  录

 

 

 

一、多态

什么是多态?

多态是指多个不同类的对象能够响应同一个方法,并产生不同的结果。

多态不是一种语法,而是一种状态,拥有一种特性(不同的类型的对象能够响应同一种方法,并产生不同的结果),即多个对象有共同的使用方法。

多态的好处?

能够方便使用者使用,大大降低使用难度。例如:我们写的USB接口协议,包含的鼠标,键盘等都拥有共同的功能,它们就是属于多态。

多态的实现?

方法:接口、抽象类(两者属于继承)和鸭子类型,都可以实现具备多态功能的代码,其中最简单的是鸭子类型。

案例:

"""
要管理 鸡 鸭 鹅
如何能够最方便的 管理,就是我说同一句话,他们都能理解
他们拥有相同的方法

"""
class J:
    def jiao(self):
        print('jjj...')

    def xidan(self):
        print('鸡蛋。。。')

class Y:
    def jiao(self):
        print('yyy...')

    def xidan(self):
        print('鸭蛋。。。')

class E:
    def jiao(self):
        print('eee...')

    def xidan(self):
        print('鹅蛋。。。')

j = J()
y = Y()
e = E()

def func(obj):
    obj.xidan()

func(j)
func(y)
func(e)

python 中包含的多态,如type() 方法     

# python中到处都有多态
a = 10
b = "10"
c = [10]

print(type(a))
print(type(b))
print(type(c))

二、OOP相关的内置函数

isinstance ()  和 issubclass()

## isinstance

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

参数1 要判断的对象 

参数2 要判断的类型 



## issubclass 

判断一个类是否是另一个类的子类 

参数一是子类

参数二是父类  

案例1:

def add_num(x,y):
    # if type(x) == int and type(y) == int:
    if isinstance(x,int) and isinstance(y,int):
        return x+y
    return None
print(add_num(10,20))  #30
print(add_num('10',20))  # None

案例2:

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

class Pig(Animal):
    def eat(self):
        print('猪就知道吃东西。。。')

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

p = Pig()
t = Tree()


def manage(obj):
    if issubclass(type(obj),Animal):  # issubclass判断是否是一个子类
        print(' 就知道吃。。。')
    else:
        print('不属于动物类!')

manage(p)
manage(t)

三、类中的魔法函数

1、__str__

__str__  会在对象被转换为字符串时,转换的结果就是这个函数的返回值 
使用场景:我们可以利用该函数来自定义,对象的是打印格式

案例:

import time
class Person:
    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('jaosn',29)
# del p
print(str(p))   # 当我们对象p转换成str字符串格式时,会自动触发__str__,结果就是该函数的返回值
time.sleep(2)
print('over')

2、析构方法  __del__

什么是析构方法?

__init__ 是构建函数,一个东西从无到有就是构建;析构就是让一个东西从有到无,如__del__

析构方法有什么特点?

当对象在内存中被释放时,会自动触发执行。

为什么需要析构方法?

如果产生的对象仅仅是用户级别的程序,那么无须定义 __del__ ,因为Python内部有自动回收资源的机制;

但是当产生对象的同时还向操作系统发起系统调用,即一个对象有用户级和内核级两种资源,如打开一个文件,创建一个数据库链接,就必须在清楚对象的同时回收系统资源,这就用到了__del__。

__del__触发的两种方式:

1、当程序出现错误会触发__del__     

class File:
    def __init__(self,name):
        self.name = name

    def say(self):
        print('hello my name is %s'%name)  # name写错了,当遇到报错的时候会自动触发__del__

    def __del__(self):
        print('__del__是什么玩意?')

f = File('jason')
f.say()

# 结果:
# NameError: name 'name' is not defined
# __del__是什么玩意?
# 结论:当程序出现错误时,会自动触发__del__

2、当程序正常运行结束后自动触发__del__ 

class File:
    def __init__(self,name):
        self.name = name

    def say(self):
        print('hello my name is %s'%self.name)  # name写错了,当遇到报错的时候会自动触发__del__

    def __del__(self):
        print('__del__是什么玩意?')

f = File('jason')
f.say()
'''
结果:
hello my name is jason
__del__是什么玩意?
结论:当程序正常运行结束后会自动触发 __del__
'''

3、__call__

执行时机:在调用对象时自动执行,(既对象加括号)

案例:

class A:
   def __call__(self,*args,**kwargs):
       print('warning!run...')
       print(*args)
       print(**kwargs)

a = A()
a(1,(2,3))  # 对象a 加括号自动调用__call__
'''
结果:
warning!run...
1 (2, 3)
'''

4、__slots__     —— 内存优化方法

前提知识:

动态容器的实现原理:

关于__slots__:

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

另外当类中出现了slots时将导致这个类的对象无法在添加新的属性 

案例:

# 原式
import sys
class Person:
    def __init__(self,name):
        self.name = name
        print(self.__dict__)

p = Person('jason')  # {'name': 'jason'}
p.age = 20  # 自己添加属性
print(p.__dict__)  # {'name': 'jason', 'age': 20}
print(sys.getsizeof(p))   # 以字节(byte)为单位返回对象大小  32

# 加入 __slots__ 对比
import sys
class Person:
    __slots__= ['name']
    def __init__(self,name):
        self.name = name
        print(self.name)

p = Person('jason')  # {'name': 'jason'}
# p.age = 20  # 无法在添加属性
# print(p.__dict__)  # 没有__dict__属性
print(sys.getsizeof(p))   # 以字节(byte)为单位返回对象大小  28

四、属性的get 、set 和 del

1、getattr setattr delattr     ——反映了python中点语法的实现原理

'''
getattr 用点访问属性的时如果属性不存在时执行
setattr 用点设置属性时
delattr 用del 对象.属性  删除属性时 执行


这几个函数反映了 python解释器是如何实现 用点来访问属性 

getattribute 该函数也是用来获取属性
在获取属性时如果存在getattribute则先执行该函数,如果没有拿到属性则继续调用 getattr函数,如果拿到了则直接返回 

'''

案例:

class A:
    def __setattr__(self,key,value):
        print('__setattr__')

    def __getattr__(self, item):
        print('__getattr__')
        return 1

    def __delattr__(self, item):
        print('__delattr__')

a = A()
a.name = 'jason'  # 在设置属性时,自动执行 __setattr__
print(a.xxx)  # 结果:__getattr__ 1  在对象访问属性的时候自动执行 __getattr__
del a.yyy    # 结果:__delattr__     在删除对象属性的时候自动执行 __delattr__

2、getitem、setitem、delitem     —— 反映了Python中 [ ] 取值的实现原理

小总结: —— 任何的符号 都会被解释器解释成特殊含义 ,例如 . [] ()

getitem 当你用中括号去获取属性时 执行
setitem  当你用中括号去设置属性时 执行
delitem 当你用中括号去删除属性时 执行

另一种解释用法:

'''
__setitem__

​ 使用key的形式添加/修改属性时触发

__getitem__

​ 使用key的形式获取属性时触发

__delitem__

​ 使用key的形式删除属性时触发

'''

案例:

class A:
    def __getitem__(self, item):
        print("__getitem__")
        # return self.__dict__[item]  # 因为self.__dict__[key] = value为执行,所以不会取到self.__dict__[item]的值

    def __setitem__(self, key, value):
        print("__setitem__")
        # self.__dict__[key] = value  # 当这句话被注释时所添加的键值对不会被保存

    def __delitem__(self, key):   
        # del self.__dict__[key]  # 因为self.__dict__[key] = value未执行,所以没有元素可以被删除
        print("__delitem__")

a = A()
# 用字典键值对的形式赋值
a['name'] = 'jason'  # __setitem__ 当添加键值对时会触发__setitem__方法

print(a['name'])  # 当按照key取值时会触发__getitem__方法

del a['name']  # 当按照key值删除keys所对应的值时会触发__delitem__方法

'''
综上代码可知:[]取值的原理就是self.__dict__在对象的名称空间(名称空间是字典的形式)中按key value 的形式进行取值操作
'''

小练习:

"""需求让一个对象支持 点语法来取值 也支持括号取值"""
class MyDict(dict):
    def __getattr__(self,item):
        return self[item]

    def _setattr__(self,key,value):
        self[key] = value


    def __delattr__(self,item):
        del self[item]
        return 999

d = MyDict()
d.name = ['jason']  # 根据__setattr__特性 自定触发并执行 self[key] = value 代码来实现,本质是字典添加键值对
# print(d.name)  # 触发__getattr__
del d.name

五、运算符重载

__gt__ 、 __lt__ 、__eq__   分别对应 大于号>  小于号<   和等于号 eq

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

案例:

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

class Student:
    def __init__(self,name,age,height):
        self.name = name
        self.age = age
        self.height = height

    def __gt__(self,other):  # 指定比较的属性 height  大于号 __gt__
        return self.height > self.height

    def __lt__(self,other):  # 指定比较的属性 height  小于号 __lt__
        return self.height < other.height

    def __eq__(self,other):   # 指定比较的属性 height  等于号 __eq__
        if self.name == other.name and self.age == other.age and  self.height == other.height:
            return True
        return False
stu1 = Student('egon',28,180)
stu2 = Student('tank',25,188)
# 开始比较大小
print(stu1>stu2)  # False
print(stu1<stu2)  # True

stu3 = Student('jason',28,180)
stu4 = Student('jason',28,180)
print(stu3 == stu4)   # True

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

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

六、迭代器协议

'''
迭代器是指具有__iter__和__next__的对象
我们可以为对象增加这两个方法来让对象变成一个迭代器 
'''

案例1:将对象变成迭代器对象

'''
迭代器是指具有__iter__和__next__的对象
我们可以为对象增加这两个方法来让对象变成一个迭代器 
'''
class MyIter:
    def __init__(self,num):
        self.num = num
        self.c = 0  # 自定义属性值的默认值

    def __iter__(self):  # 迭代器调用 __iter__还是迭代器本身
        return self

    def __next__(self):
        self.c += 1
        if self.c < self.num:
            return '嘿嘿'
        else:
            raise StopIteration  #  异常处理   注意:当调用__next__取不到值的时候会报错 StopIteration

m = MyIter(10)
# 迭代器的取值方式不依赖索引,可以用for循环取值。。。
for i in m:
    print(i)  # 

小练习:

"""实现一个自定义的range"""
class MyRange:
    def __init__(self,start_num,end_num,step):
        self.start_num = start_num - 2
        self.end_num = end_num
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        self.start_num += self.step
        if self.start_num < self.end_num:
            return self.start_num
        else:
            raise StopIteration    # 处理异常

mr = MyRange(1,10,2)
# for循环取值迭代器对象
for i in mr:
    print(i)

七、上下文管理

上下文 context

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

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

涉及到的两个方法:enter()   exit()

enter

表示进入上下文,(进入某个场景 了)

exit

表示退出上下文,(退出某个场景 了)

 

当执行with 语句时,会先执行enter ,

当代码执行完毕后执行exit,或者代码遇到了异常会立即执行exit,并传入错误信息

包含错误的类型.错误的信息.错误的追踪信息

注意:

enter 函数应该返回对象自己 
exit函数 可以有返回值,是一个bool类型,用于表示异常是否被处理,仅在上下文中出现异常有用
如果为True 则意味着,异常以及被处理了 
False,异常未被处理,程序将中断报错

案例:

class MyOpen(object):


    def __init__(self,path):
        self.path = path

    def __enter__(self):
        self.file = open(self.path)
        print("enter.....")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit...")
        # print(exc_type,exc_val,exc_tb)
        self.file.close()
        return True


with MyOpen("a.txt") as m:
    # print(m)
    # print(m.file.read())
    "123"+1

# m.file.read()