1. 鸭子类型和多态
鸭子类型
- 鸭子类型是动态类型的一种风格,不是由继承特定的类或者实现特定的接口,而是当前的方法和属性的集合所决定的。
- 鸭子类型关注的不是对象的类型本身,而是它如何使用
- 动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
- 在一下代码中的animal_l 列表中,Cat、Dog和Duck都是变量,而只有在调用的同时给类对象加上() 才能够调用 类对象里面的方法。否则会抛出异常。
class Duck(object):
def walk(self):
print('I am a duck')
def swim(self):
print('I can swim')
class Cat(object):
def walk(self):
print('I am a cat')
def swim(self):
print('I can swim')
class Dog(object):
def walk(self):
print('I am a dog')
def swim(self):
print('I can swim')
animal_l = [Duck, Cat, Dog]
for animal in animal_l:
# animal.walk() # TypeError: walk() missing 1 required positional argument: 'self'
animal().walk()
- 在看网上的经典代码:
class Duck(object):
def walk(self):
print('I am a duck and I can walk')
def swim(self):
print('I am a duck and I can swim')
class Cat(object):
def walk(self):
print('I am a cat and I can walk')
def swim(self):
print('I am a cat and I can swim')
class Dog(object):
def walk(self):
print('I am a dog and I can walk')
def swim(self):
print('I am a dog and I can swim')
dog = Dog()
duck = Duck()
cat = Cat()
def swim_walk(animal):
animal.walk()
animal.swim()
print(swim_walk(cat))
# print(swim_walk(dog))
# print(swim_walk(duck))
在以上的代码中,函数swim_walk的参数animal时任意类型,它可以接受任意类型的参数。而Dog类、Duck类和Cat类有着一样的方法,当一个函数调用Cat类,并且用到了两个方法walk()和swim()。我们传入Dog类或者Duck类一样可以运行,函数并不会检查传入的是不是Cat类,只要它拥有 walk() 和swim() 方法就可以。
- 鸭子类型的特定总结:
- 变量绑定的 类型具有不确定性
- 函数可以接受任意类型的参数
- 调用方法时不检查提供的参数类型
- 调用是否成功由参数的属性和方法决定
- 调用不成抛出错误,也就是调用的对象中没有实现该功能的方法
多态
- 定义类型与运行类型不一样就是多态
- Python 不支持多态,也不用支持多态,而是一种多态语言。 多态的概念时应用于Java 和 c# 这一类强类型语言中
2. 抽象基类(abc模块)
抽象基类的简单介绍
- 抽象基类就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。抽象基类不能被实例化,经常作为基类供子类继承,子类中重写虚函数,实现具体的接口
- 简单来说,抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
- 抽象基类的特点(主要用于接口设计):
- 规定继承类必须具有抽象基类指定的方法
- 抽象基类无法实例化
- 注意:python 中并没有提供抽象类和抽象方法,但是提供了内置模块 abc 来模拟实现抽象类
- 抽象基类的应用场景
- 检查某个基类中是否含有某种方法
- 强制子类必须实现某些方法
class demo(object):
def __init__(self, l):
self.l = l
def __len__(self):
return len(self.l)
list = [1, 2, 3, 4]
d = demo(list)
print(len(d))
# 之所以输出的结果会是4。是因为demo 类中含有__len__ 的方法中刚好返回 renturn len(self.l) 所以返回的结果会是4
- 如何判断 对象中含有某种方法呢,以 d 为例
print(hasattr(d, '__len__')) # 判断的是 d 中是否含有__len__方法
hasattr 的源码如下:
- hasattr 的源码如下:我们可以通过源码得知 hasattr 返回的类型的判断,返回的结果也就是布尔值
def hasattr(*args, **kwargs): # real signature unknown
"""
Return whether the object has an attribute with the given name.
This is done by calling getattr(obj, name) and catching AttributeError.
"""
- 判断 实例化对象 d 中是否含有__len__ 方法,也是判断d 是否是Sized 的子类
from collections.abc import Sized # 判断d 是否是 Sized 这个类
print(isinstance(d, Sized)) # True isinstance 判断的是 d 是否是 Sized 的一个对象
Sized 的源码如下:
- 观察以下的源码可以发现,Sized类是通过判断d 是否是Sized的子类 进而判断d 中是否含有__len__ 方法。 该类也可以进行其他方法的判断
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
注意:
- 注册的虚拟子类不会继承抽象基类的任何方法和属性
- 如果我们在父类 中主动抛出异常,我们不重写子类的方法的话,结果会输出异常
class CacheBase(object):
def create(self):
raise NotImplemented
def dele(self):
raise NotImplemented
class RedisBase(CacheBase):
pass
# def dele(self):
# pass
r = RedisBase()
print(r.create()) # TypeError: exceptions must derive from BaseException
- 使用抽象基类重写之后
import abc
class CacheBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def create(self):
raise NotImplemented
def dele(self):
raise NotImplemented
class RedisBase(CacheBase):
def create(self):
print('create')
r = RedisBase()
print(r.create()) # create
注意:我们为什么使用抽象基类进行重写呢?
- 使用的抽象基类的强制重写装饰器 可以使子类继承父类的时候,强制对某些特定的方法进行重写,否则会抛出错误
- 我们使用正常方法重写的时候,如果子类没有对父类的方法进行重写,然后实例化类对象的时候,就算父类中主动抛出异常,最后的输出结果上也不会出现异常
class CacheBase(object):
def create(self):
raise NotImplemented
def dele(self):
raise NotImplemented
class RedisBase(CacheBase):
pass
r = RedisBase()
- 使用抽象基类装饰器去重写的时候,没有进行方法的重写就算不去访问父类的方法,但是结果也会抛出异常
import abc
class CacheBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def create(self):
pass
def dele(self):
pass
class RedisBase(CacheBase):
pass
r = RedisBase()
# TypeError: Can't instantiate abstract class RedisBase with abstract methods create
collection.abc模块常用的抽象基类::
__all__ = ["Awaitable", "Coroutine",
"AsyncIterable", "AsyncIterator", "AsyncGenerator",
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
"Sized", "Container", "Callable", "Collection",
"Set", "MutableSet",
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence",
"ByteString",
]
3. type 与 isinstance 的区别
- type 不考虑继承关系
- isinstance 考虑继承关系
class A(object):
pass
class B(A):
pass
b = B()
print(isinstance(b, B)) # True
print(isinstance(b, A)) # True
print(type(b)) # <class '__main__.B'>
# 判断内存地址
print(type(b) is B) # True
print(type(b) is A) # False
4. 类属性和实例属性
- 查找顺序
- 对象是可以向上查找的,所以实例属性可以访问到类属性
- 如果对象拥有自己实例属性,则输出的就是自己的
- 类只能访问到类属性,不能向下访问实例属性
class A(object):
cls = 1
def __init__(self, z, l):
self.z = z
self.l = l
a = A(3, 4)
print(a.cls, a.z, a.l) # 1 3 4
print(A.cls) # 1
print(A.z) # AttributeError: type object 'A' has no attribute 'z'
- 多继承查找顺序
- 继承关系如下:
- 继承关系如下又该如何继承:
- 继承关系如下:
注意:
- 继承顺序的查看可以依据
__mro__
方法
# 菱形继承
class A(object):
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
5. Python的自省机制
-
在日常生活中,自省就是自我检查的一种行为
-
在计算机编程中,自省是指:检查某些事物以确定它是什么、它知道什么、它能够做什么。自省提供了极大的灵活性和控制力
-
简单来说:自省就是面向对象的语言所写的程序在运行的时候,能够知道对象的类型。也就是说,运行时能够获知对象的类型
-
python中常用的自省机制(函数)有:dir()、type()、hasattr()和isinstance(),通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某种属性,访问对象的属性。
-
dir()
- dir() 函数可能是最知名的自省机制了。它返回传递给它任何对象的属性名称经过排列的列表。如果不指定对象,则dir() 返回当前作用域中的名称。
import keyword
print(dir(keyword))
# ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'iskeyword', 'kwlist', 'main']
-
type()
- type() 函数有助于我们确定对象是字符串还是整数,或其它类型的对象
-
hasattr()
- 对象拥有属性,并且dir() 函数会返回这些属性的列表。但是,有时候我们只想测一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 hasattr() 和 getattr() 函数来完成。
print(hasattr(id, '__doc__'))
# True
print(isinstance('python', str))
# True
6. super 函数
- 如果我们想要使用父类中的方法,以下的方法不使用继承会造成代码的冗余
class Teacher(object):
def __init__(self, age, name, weight):
self.age = age
self.name = name
self.weight = weight
def speak(self):
print('我叫{}, 我{}岁, 我体重{}kg'.format(self.name, self.age, self.weight))
class Student(Teacher):
def __init__(self, age, name, weight, height):
self.age = age
self.name = name
self.weight = weight
self.height = height
def speak(self):
print('我叫{}, 我{}岁, 我体重{}kg, 我身高{}cm'.format(self.name, self.age, self.weight, self.height))
s = Student(22, 'g', 70, 180)
print(s.speak())
- 如果使用下面的继承方法,又会指定的是特定的类
class Teacher(object):
def __init__(self, age, name, weight):
self.age = age
self.name = name
self.weight = weight
def speak(self):
print('我叫{}, 我{}岁, 我体重{}kg'.format(self.name, self.age, self.weight))
class Student(Teacher):
def __init__(self, age, name, weight, height):
Teacher.__init__(self, age, name, weight)
self.height = height
def speak(self):
print('我叫{}, 我{}岁, 我体重{}kg, 我身高{}cm'.format(self.name, self.age, self.weight, self.height))
s = Student(22, 'g', 70, 180)
print(s.speak())
- 使用super 函数继承 可以省去很不顾虑
class Teacher(object):
def __init__(self, age, name, weight):
self.age = age
self.name = name
self.weight = weight
def speak(self):
print('我叫{}, 我{}岁, 我体重{}kg'.format(self.name, self.age, self.weight))
class Student(Teacher):
def __init__(self, age, name, weight, height):
super().__init__(age, name, weight)
self.height = height
def speak(self):
print('我叫{}, 我{}岁, 我体重{}kg, 我身高{}cm'.format(self.name, self.age, self.weight, self.height))
s = Student(22, 'g', 70, 180)
print(s.speak())