鸭子类型和多态
多态的概念是应用于java和C#这一强类型语言中,而python崇尚“鸭子类型”。
动态语言用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,并不要求严格的继承体系,一个对象只要“看起来像鸭子,走路像鸭子”,那它就可以被看做是鸭子。
多态简单理解,定义时的类型和运行时的类型不一样
抽象基类
抽象基类(abstract base class,ABC):抽象基类就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。抽象基类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体的接口。
抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
特点
- 要定义但是并不完整的实现所有方法
- 基类的意思是作为父类
- 父类要明确表示出方法的特征,这样在写子类时更加简单明白
应用
- 用作父类
- 用作检验实例类型
- 用作抛出异常说明
from collections.abc import Sized, Container
class People(object):
def __init__(self, name):
self.name = name
# 如果没有__len__魔法方法,不能判断类中字符串的长度
def __len__(self):
return len(self.name)
def __contains__(self, item):
self.item = item
a = People(['cat', 'dog'])
print(len(a))
# 判断a对象中是否包含__len__属性
print(hasattr(a, '__len__'))
# 判断a对象是否是Sized类型
print(isinstance(a, Sized))
print(isinstance(a, Container))
# 2
# True
# True
# True
hasttr(object, name) 返回object对象是否具有该name属性
isinstance(object, A_tuple): 返回对象是否属于该类或是其子类,如果是tuple,判断改对象是否属于该tuple
from collections.abc import Sized, Container
print(isinstance(a, Sized))
print(isinstance(a, Container))
# 在python文件夹下,lib->_collections_abc.py可以看到该库的源码
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
如果类中有__len__,name它就是一个Sized类型,Sized方法就是一个抽象基类
import abc
# 定义抽象基类
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
def eat(self, food):
pass
@abc.abstractmethod
def voice(self, sound):
pass
class Dog(Animal):
# 子类中方法重写
def eat(self, food):
pass
# 方法重写
def voice(self, sound):
pass
d = Dog()
d.eat('bone')
子类中方法必须重写,否则报错
isinstance 与 type 的区别
- type 不考虑继承关系
- isinstance 考虑继承关系
class B(object):
pass
class C(B):
pass
b = C()
print(isinstance(b, C))
print(isinstance(b, B))
print(type(b) is C)
print(type(b) is B)
# True
# True
# True
# False
== 与 is 的区别
- ==是值相等
- is是内存地址是否相等
x = y = [1, 2]
z = [1, 2]
print(x == z)
print(x is y)
print(x is z)
print(id(x))
print(id(y))
print(id(z))
# True
# True
# False
# 2041717547528
# 2041717547528
# 2041717547592
对象变量
class A(object):
# 类属性
aa = 22
# 实例属性
def __init__(self, x, y):
# 实例方法
self.x = x
self.y = y
a = A(1, 2)
print(id(a))
print(id(A))
A.aa = 11
print(id(a))
print(id(A))
b = A(1, 2)
print(id(b))
# 1918674761712
# 1918640702312
# 1918674761712
# 1918640702312
# 1918674761600
当改变A.aa时,是在原内存地址上修改了值,id不变
当创建b对象时,会在内存空间中重新开辟一块区域,所以id不同
类属性和实例属性的查找顺序
python2.3之后采用了C3算法(大佬文章):https://www.cnblogs.com/blackmatrix/p/5644023.html
# 假设一个类继承关系如下
class D:
pass
class B(D):
pass
class E:
pass
class C(E):
pass
class A(B, C):
pass
# 通过__mro__方法可以查看继承的顺序
print(A.__mro__)
# (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)
# ABDCE
python对象的自省机制
自省是通过一定的机制查询到对象的内部结构
比较常见的自省(introspection)机制有:dir(),type(),hasttr(),instance(),通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某个属性。
class Person(object):
name = 'shell'
class Student(Person):
def __init__(self, school):
self.school = school
s = Student('北大')
print(dir(s))
print(s.__dict__)
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'school']
# {'school': '北大'}
有一定的局限性,弗雷中的属性不能访问到
a = [1, 2]
print(dir(a))
print(dir(list))
上面两个结果是一样的,说明dir只是在访问list()中有哪些方法,并不能访问值
super函数
在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用super来实现。
class People(object):
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self):
print('%sspeak:i am %d years old' % (self.name, self.age))
class Student(People):
def __init__(self, name, age, grade):
super().__init__(name, age)
self.grade = grade
s = Student('jd', 18, 3)
s.speak()
print(Student.__mro__)
即使有super函数类的执行顺序同样是满足mro算法的