Python高级编程技巧(鸭子类型、抽象基类、type和isinstance、类属性和实例属性查找顺序、自省机制)
一、鸭子类型与多态
多态:
定义时的类型和运行时的类型不一样,就成为多态。
鸭子类型:
多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。
动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做鸭子。
比方说,我们在扩展一个已有列表的时候,使用li.extend(seq)
,参数seq只要是一个可迭代的对象就可以了,比如元组、字符串或其它列表,并没有规定具体的某种格式,这就是说只要看起来像“鸭子”(可迭代),就可以使用。
再举个例子:
class Cat(object): def info(self): print('cat') class Dog(object): def info(self): print('dog') class Pig(object): def info(self): print('pig') animal_li = [Cat,Dog,Pig] for animal in animal_li: animal().info() ------------------------ 结果:
cat
dog
pig
关于上述案例有几点想说明一下:
- 以往的学习中,我们往往是将一个类实例化后赋予一个变量,通过变量来调用类中的方法,如:
a = Demo() a.test()
在这个案例中,我们需要树立的一个观念就是,类名() 是指代的对象,在赋予一个变量a之后,变量a仍然指向了类名()。因而通过class().test()这种方式是可以调用类内部的方法的。
-
程序自上而下执行,在执行到
animal_li = [Cat,Dog,Pig]
的时候,中括号内部的元素程序依然认为是变量,直到在for循环中,采用后缀小括号的时候才会将其认定为类名,这就是一种多态现象。定义时和运行时的类型不一样。
二、抽象基类(abc模块)
1. 介绍
抽象基类(abstract base class, ABC)就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。抽象基类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体接口。
抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现(重写)这些方法,否则无法实例化。
打个比方,抽象基类就像是个设计图,是个模板,子类按照这个模板必须自己制造配件(重写虚函数),所以我们说模板自身是无法实例化的,里面的纯虚函数就相当于提供了一个接口,供子类继承。
2. 应用场景
- 我们去检查某个类中是否有某种方法;
- 我们需要强调某个子类必须实现(重写)某些方法。
我们先来看第一个应用场景,比如判断一个Demo类中是否含有__ len__()魔法方法。该方法的作用是当我们使用len(obj)命令的时候,会触发obj所属类中的__len__()函数,例如:
class Demo(object): def __init__(self,li): self.li = li def __len__(self): return len(self.li) l = [1,2,3] d = Demo(l) # 列表对象是可以作为参数传入 print(len(d)) -------------------- 结果: 3
现在我们要判断Demo类中是否有__ len__()魔法方法,我们第一个想到的可以是采用hasattr的方法:
res = hasattr(d,'__len__') # print(res) -------------- 结果: True
再者就是通过抽象基类,此处推荐使用方法isinstance()。
python3提供的collections文件夹中有一个abc模块,其中定义了Sized类,这是个抽象基类,通过该类可以判断某对象中是否含有__ len__()方法。
from collections.abc import Sized: print(isinstance(d,Sized)) -------------------- 结果: True
Sized的源码如下 :
class Sized(metaclass=ABCMeta): __slots__ = () @abstractmethod def __len__(self): return 0 @classmethod def __subclasshook__(cls, C): if cls is Sized: return _check_methods(C, "__len__") return NotImplemented
在这里说明一下,关于Sized为何能够判断len方法是否存在,目前个人还无法知晓其中原理,为了保持进度留待以后再说。只是这里先介绍抽象基类的这么一个功能。
接下来看抽象基类的第二个功能,强调子类必须实现(重写)某种方法。
为了实现这个功能,我们可以在父类中主动抛出异常,如:
class CacheBase(object): def test(self): raise NotImplementedError class RedisBase(CacheBase): pass a = RedisBase() a.test() ---------------------- 结果:
NotImplementedError
这样一来,如果不在RedisBase子类中重写test方法,调用test方法就会报错。
那如何通过抽象基类来实现这个功能呢?
import abc # 导入abc模块 class CacheBase(metaclass=abc.ABCMeta): # 表明关键字metaclass=abc.ABCMeta,继承了抽象基类,不再是Object @abc.abstractmethod # 装饰器不能少 def test(self): pass class RedisBase(CacheBase): pass a = RedisBase() ---------------- 结果:
TypeError: Can't instantiate abstract class RedisBase with abstract methods test
从上述案例可以看出,采用抽象基类方法来实现子类方法必须重写的功能,在实例化步骤就已经会报错了,这种强制性更加严格。这要求我们在写子类的时候必须修改方法。
与此同时,类CacheBase也变成了抽象基类,不可被实例化。
三、type和isinstance
二者的区别在于:
- type不考虑继承关系;
- isinstance考虑继承关系。
class Father(object): pass class Son(Father): pass a = Son() print(isinstance(a,Son)) # True print(isinstance(a,Father)) # True print(type(a)) # <class '__main__.Son'> print(type(a) is Son) # True print(type(a) is Father) # False
根据以上案例可以看出,isinstance是考虑继承关系的。
需要注意一下,type(a)
返回的是对象a的数据类型,这里返回的结果说明对象a是Son类型。
另外,针对type,个人想补充一个关于创建类的新方法。
在之前创建类的方法中,我们都是通过class 类名(object)
来创建一个新类,其实type也可以创建新类:
type(name,bases,dict) # type(类名,父类的元组(表继承,可以为空),包含属性的字典(名称和值)),返回一个新类
举一个例子:
def Test(self): print('111') Demo = type('Demo', (object,), dict(test=Test)) # 支持多继承,但是不要忘记tuple的单元素写法,如此处的(object,) a = Demo() a.test() ----------------- 结果: 111
我们来看一下两个过程:
a = Demo() # 对象a的实例化过程 Demo = type(name,bases,attrs) # 新类的type法建立过程
以上两种方法虽然意义不同,但形式结构类似,讲述这一点的目的就是为了让自己更好的理解为何Demo类的类型是type,即:
class Demo(object): pass a = Demo() print(type(a)) # <class '__main__.Demo'> print(type(Demo)) # <class 'type'>
type是建立类的元类,日后再讲到。
四、类属性和实例属性
1. 基本查找顺序
- 对象是可以向上查找的,所以可以访问到类属性;
- 当对象自己有该实例属性的时候,则优先访问自己的;
- 类不能向下查找,只能访问到类属性。
举个例子:
class Demo(object): info = 1 def __init__(self,name):
self.name = name
a = Demo('Tom') print(a.info) # 通过对象可以访问类属性 print(Demo.name) # 报错,通过类不可以访问实例属性 a.info = 2 print(a.info) # 给对象a添加实例属性,再次访问则优先访问自己的实例属性,输出为2
2. 多继承查询顺序
python3 新式类引入C3算法,我们可以通过className.__mro__
查找继承顺序。
关于多继承顺序在之前的章节中已经有所提及,我们主要需要注意的点就是菱形继承:
class D(object): pass class B(D): pass class C(D): pass class A(B,C): pass print(A.__mro__) ----------------------- 结果: (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
可以看出,继承顺序为ABCD。
五、Python对象的自省机制
自省是通过一定的机制查询到对象的内部结构。
Python中比较常见的自省(introspection)机制(函数用法)有:dir()、type()、hasattr()、isinstance(),通过这些函数,我们能够在运行时得知对象的类型,判断对象是否存在某个属性,访问对象的属性。
需要说明的是,dir()会返回继承的父类的一系列成员,与__ __dict____的区别在之前的文章有讲过,如若忘记了可以翻看或者百度一下。
本文地址:https://blog.csdn.net/Beron_Lee/article/details/108100005
上一篇: 七夕节应该吃什么?
下一篇: 处暑吃啥水果比较好呢?