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

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

  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____的区别在之前的文章有讲过,如若忘记了可以翻看或者百度一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值