Day26 多态、封装、反射

一. 多态

实现多态的步骤:

  1. 定义一个父类(Base),实现某个方法(比如:run)
  2. 定义多个子类,在子类中重写父类的方法(run),每个子类run方法实现不同的功能
  3. 假设我们定义了一个函数,需要一个Base类型的对象的参数,那么调用函数的时候,传入Base类不同的子类对象,那么这个函数就会执行不同的功能,这就是多态的体现。

一个父类,他具有多个子类,不同的子类在调用相同的方法,执行的时候产生不同的形态,这个叫多态

import abc 
class Animal(metaclass=abc.ABCMeta): # 同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): # 动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): # 动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): # 动物的形态之三:猪
    def talk(self):
        print('say aoao')

peo = People()
dog = Dog()
pig = Pig()

# peo、dog、pig都是动物,只要是动物肯定有talk方法
# 于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

# 更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()
  • 增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
  • 增加了程序额可扩展性,通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用 

>>> class Cat(Animal): # 属于动物的另外一种形态:猫
...     def talk(self):
...         print('say miao')
... 
>>> def func(animal): # 对于使用者来说,自己的代码根本无需改动
...     animal.talk()
... 
>>> cat1=Cat() # 实例出一只猫
>>> func(cat1) # 甚至连调用方式也无需改变,就能调用猫的talk功能
say miao

这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1) 

多态实际上是依附于继承的两种含义的:“改变”和“扩展”,本身就意味着必须有机制去自动选用改变/扩展过的版本,而多态就是这两层意义的一个具体的实现机制。对扩展开放,对修改封闭:允许新增子类,但不需要修改依赖父类类型的函数、方法

对于静态语言(如JAVA)来说,如果需要传入一个类型,如Animal类型,则传入的对象必须是Animal类型或者它的子类,否则无法调用类型里的方法。

但对于Python这样的动态语言来说,则不一定需要传入Animal类型,只需要保证传入的对象有这个方法就可以,如Animal中有一个方法run(),创建一个新对象Timer(不继承于Animal),也有一个run()方法,也是可以调用。

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象,也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

# 二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pas

二. 封装

约定:用双下划线开头的方式命名的是隐藏属性

# 其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
# 类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:

class A:
    __N=0 # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 # 变形为self._A__X
    def __foo(self): # 变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() # 只有在类内部才可以通过__foo的形式访问到.

# A._A__N是可以访问到的,
# 这种,在外部是无法通过__x这个名字访问到,import的时候不会导入

这种机制并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问,使用者也应该遵守约定不访问这样的属性

封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性

  1. 封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
  2. 封装方法:目的是隔离复杂度

三. 反射

【模块也是对象,同样可以使用反射的方法来操作】

也可以称为自省,主要指程序可以访问、监测和修改它本身状态或行为的一种能力

四个可以实现自省的函数,可以直接操作一个类或对象的状态

  • getattr(obj, attr, default=None):attr都是字符串类型,没有会报错
  • setattr(obj, attr, v)
  • hasattr(obj, attr)
  • delattr(obj, attr)
>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()

>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

# 传入一个defualt参数,如果试图获取不存在的属性,会抛出AttributeError的错误
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

【注!只有在不知道对象信息的时候,才会需要去获取对象信息】

使用反射的好处

  1. 实现可插拔机制,事先定义好接口,接口只有在被完成后才会真正执行
  2. 动态导入模块

__import__(pack.model):只能获得顶级模块名pack

import_module(pack.model):可以直接获取到对应模块名pack.model

__setattr__、__delattr__、__getattr__方法

class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __getattr__(self, item):		#只要获取属性且属性不存在的时候就会调用
        print('----> from getattr:你找的属性不存在')
    def __getattribute__(self, item):		#无论属性存在,都会执行
 print('----->')
 raise AttributeError('error')	#当不存在时,抛出AttributeError,然后__getattr__接收异常


    def __setattr__(self, key, value):		#只要设置属性的时候就会调用
        print('----> from setattr')
        # self.key=value #这就无限递归了,你好好想想
        # self.__dict__[key]=value #应该使用它

    def __delattr__(self, item):		#只要删除属性的时候就会调用
        print('----> from delattr')
        # del self.item #无限递归了
        self.__dict__.pop(item)

#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)

#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)

#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx

包装

当需要基本标准数据类型来定义自己的数据类型,新增或改写方法时,通过继承和派生可以实现

class List(list): # 继承list所有的属性,也可以派生出自己新的,比如append和mid
    def append(self, p_object):
        ' 派生自己的append:加上类型检查'
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        super().append(p_object)

    @property
    def mid(self):
        '新增自己的属性'
        index=len(self)//2
        return self[index]

l=List([1,2,3,4])
print(l)
l.append(5)
print(l)
# l.append('1111111') # 报错,必须为int类型

print(l.mid)

# 其余的方法都继承list的
l.insert(0,-123)
print(l)
l.clear()
print(l)

授权

授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能,其他保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。实现授权的关键点就是覆盖__getattr__方法。

import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        self.file = open(filename,mode,encoding=encoding)		# 授权给对象的默认属性
    def write(self,line):
        t = time.strftime('%Y-%m-%d %T')
        self.file.write('%s %s' %(t,line))

    def __getattr__(self, item):
        return getattr(self.file,item)	# 原有的方法都是调用已有的实现

f1 = FileHandle('b.txt','w+')
f1.write('你好啊')
f1.seek(0)
print(f1.read())
f1.close()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值