欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python高级编程技巧(鸭子类型、抽象基类、type和isinstance、类属性和实例属性查找顺序、自省机制)

程序员文章站 2022-07-11 12:35:17
鸭子类型与多态多态:定义时的类型和运行时的类型不一样,就成为多态。鸭子类型:多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。...

一、鸭子类型与多态

多态
定义时的类型和运行时的类型不一样,就成为多态。

鸭子类型
多态的概念是应用于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 

关于上述案例有几点想说明一下:

  1. 以往的学习中,我们往往是将一个类实例化后赋予一个变量,通过变量来调用类中的方法,如:
a = Demo() a.test() 

在这个案例中,我们需要树立的一个观念就是,类名() 是指代的对象,在赋予一个变量a之后,变量a仍然指向了类名()。因而通过class().test()这种方式是可以调用类内部的方法的。

  1. 程序自上而下执行,在执行到animal_li = [Cat,Dog,Pig]的时候,中括号内部的元素程序依然认为是变量,直到在for循环中,采用后缀小括号的时候才会将其认定为类名,这就是一种多态现象。定义时和运行时的类型不一样。

二、抽象基类(abc模块)

1. 介绍

抽象基类(abstract base class, ABC)就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。抽象基类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体接口。

抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现(重写)这些方法,否则无法实例化。

打个比方,抽象基类就像是个设计图,是个模板,子类按照这个模板必须自己制造配件(重写虚函数),所以我们说模板自身是无法实例化的,里面的纯虚函数就相当于提供了一个接口,供子类继承。

2. 应用场景

  1. 我们去检查某个类中是否有某种方法;
  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

二者的区别在于:

  1. type不考虑继承关系;
  2. 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. 基本查找顺序

  1. 对象是可以向上查找的,所以可以访问到类属性;
  2. 当对象自己有该实例属性的时候,则优先访问自己的;
  3. 类不能向下查找,只能访问到类属性。

举个例子:

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