一. 多态
实现多态的步骤:
- 定义一个父类(Base),实现某个方法(比如:run)
- 定义多个子类,在子类中重写父类的方法(run),每个子类run方法实现不同的功能
- 假设我们定义了一个函数,需要一个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的时候不会导入
这种机制并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问,使用者也应该遵守约定不访问这样的属性
封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性
- 封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
- 封装方法:目的是隔离复杂度
三. 反射
【模块也是对象,同样可以使用反射的方法来操作】
也可以称为自省,主要指程序可以访问、监测和修改它本身状态或行为的一种能力
四个可以实现自省的函数,可以直接操作一个类或对象的状态
- 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
【注!只有在不知道对象信息的时候,才会需要去获取对象信息】
使用反射的好处
- 实现可插拔机制,事先定义好接口,接口只有在被完成后才会真正执行
- 动态导入模块
__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()