鸭子类型
鸭子类型的概念来源于 James Whitcomb Riley 提出的鸭子测试。如果一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么它就是鸭子。
也就是说,鸭子类型不关注对象的类型,不用在乎“只能你是鸭子,它不能是鸭子”,而是关注对象具有的行为(方法)。即关注的不是对象的类型本身,而是它是如何使用的。类与类之间不用共同继承一个父类,只需要让它们有相同的方法,相同的行为即可。 鸭子类型在python中经常使用到,python属于动态语言,不需要声明变量类型,而是在解释器中运行时候动态写入改变其结构。 比方说,python中的extend方法的参数是接收一个可迭代的对象,如list、tuple、和set对象。但是并没有规定只有这些固定的对象才可以被接收,只要有共同的行为——可迭代iterable就可以。上诉内置的对象都实现了__iter__(self)(定义当迭代容器中的元素的行为),这个__iter__(self)就代表了相同的行为。这就是所谓的,它不关注对象的类型,而是关注对象具有的行为(方法)。一个对象的特征不是由父类决定,而是通过对象的方法决定。
多态
我们都知道,Python 是弱类型语言,其最明显的特征是在使用变量时,无需为其指定具体的数据类型。这会导致一种情况,即同一变量可能会被先后赋值不同的类对象。 python中类的多态特性,还要满足以下 2 个前提条件: - 继承:多态一定是发生在子类和父类之间; - 重写:子类重写了父类的方法。
class CLanguage:
def say(self):
print("调用的是 Clanguage 类的say方法")
class CPython(CLanguage):
def say(self):
print("调用的是 CPython 类的say方法")
class CLinux(CLanguage):
def say(self):
print("调用的是 CLinux 类的say方法")
a = CLanguage()
a.say()
a = CPython()
a.say()
a = CLinux()
a.say()
当两个类是鸭子类型,那么它们就有相同的方法say(),同一变量在执行这个相同的方法时,由于 a 实际表示不同的类实例对象,因此 a.say() 调用的并不是同一个类中的 say() 方法,这就是多态。
抽象基类(abc模块)
Python是动态语言,没有变量类型,变量名只是一个符号,所以就不能直接通过变量名来判断属于哪种类型,也不能在编程的过程中做类型检查。Python不是通过继承某个类来获得某些方法,而是通过在类中实现某种魔法方法来表明这个类的作用。
所以在Python中我们需要通过某种形式来判断一个类属于哪种类型,就是通过和实现了这个魔法方法的基类做比较,看这个类中是否实现了这种特定的魔法方法。这个被其他类用来比较的基类,就叫做抽象基类。
由abc.ABCMeta这个元类实现的类就是抽象基类。抽象基类基于鸭子类型,其实更像是一种协议,一种强制规范。在抽象基类中实现某种方法,所有继承这个抽象基类的类都必须覆盖这些方法
抽象基类无法实例化
应用场景: - 判断一个类是否是某种类型,就要用到一个抽象基类Sized ```python from collection.adc import Sized
class Company:
def __init__(self, employee_list):
self.employee = employee_list
def __len__(self):
return len(self.employee)
com = Company(["Tom", "Jerry"])
isinstance(com, Sized)
```
> 尽量使用isinstance而不是type判断一个对象的类型,因为isinstance会展示其继承关系,子类创建的对象同样属于子类所继承的父类强制某个子类必须实现某些方法。比如说在实现一个web框架时,常常需要设计一个抽象基类,指定子类必须实现某种方法。 ```python import abc
class CacheBase(metaclass=abc.ABCMeta): @abc.abstractmethod def get(self, key): pass
@abc.abstractmethod def set(self, key, value): pass
class RedisCache(CacheBase): pass
redis_cache = RedisCache() 因为在继承了抽象基类的子类中,没有重写抽象基类中的get()、set()方法,所以报错。shell TypeError: Can't instantiate abstract class RedisCache with abstract methods get, set ```
类变量(类属性)和实例变量(实例属性)
无论是类变量(类属性)还是实例变量(实例属性),都无法向普通变量或者函数那样,在类的外部直接使用它们。
关于类变量: - 类体中、所有函数之外:此范围定义的变量,称为类属性或类变量; - 类变量可以通过类来直接访问和修改; - 类变量是所有实例所共有的,所以实例也可以调用,但是不可以修改,如果通过实例修改类变量,实际上是创建了新的实例变量并赋值。
关于实例变量: - 类体中,所有函数内部:以“self.变量名”的方式定义的变量,称为实例属性或实例变量; - 实例变量只能通过实例访问和修改,不能通过类访问到。
属性查找顺序——MRO
在类中是自下而上查找,先查找该变量名的实例变量,如果没有再去查找该变量名的类变量。 类之间的查找使用的是C3算法,可以使用mro方法找到通过这个类查找某个属性的查找顺序。
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
print(A.__mro__)
(, , , , )
类方法、静态方法、实例(对象)方法类方法 默认有个 cls 参数,可以被类和对象调用,需要加上 @classmethod 装饰器。
静态方法 用@staticmethod 装饰的不带 self 参数的方法叫做静态方法,类的静态方法可以没有参数,可以直接使用类名调用。
实例方法 第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法),只能由实例对象调用。
数据封装和私有属性
主要是为了防止误用。两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。在外部访问可以使用._classname__private_attrs。
自省机制
自省就是面向对象的语言所写的程序,通过一定的机制能够查询到对象的内部结构。 Python中比较常见的自省(introspection)机制(函数用法)有: - dir():返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir() 返回当前作用域中的名称。 - type():type() 函数有助于我们确定对象是字符串还是整数,或是其它类型的对象。 - hasattr():对象拥有属性,并且 dir() 函数会返回这些属性的列表。但是,有时我们只想测试一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 hasattr() 和 getattr() 函数来完成,返回True或False。 - isinstance():测试对象,以确定它是否是某个特定类型或定制类的实例,并且能保对象所在类的继承关系,即某个对象是一个子类的实例,也是子类的父类的实例。
通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某个属性,访问对象的属性。
super()
python3中,可以直接使用super().父类方法名来调用父类,在遇到多继承的情况下,调用super(),返回MRO查找序列中的下一个。
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
print("B")
super().__init__()
with
with是一种上下文管理协议,目的在于简化try…except…finlally的处理流程。
with通过__enter__方法初始化,然后在__exit__中做善后以及处理异常。所以使用with处理的对象必须有__enter__()和__exit__()这两个方法。 - __enter__():__enter__()方法在语句体(with语句包裹起来的代码块)执行之前进入运行。 - __exit__():__exit__()方法在语句体执行完毕退出后运行。
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。