本小节介绍了鸭子模型、抽象基类、类变量和实例变量、python中类的继承顺序、对象的私有属性、对象的自省机制、super函数。
1.鸭子类型和多态
鸭子类型
在维基百科中这样描述鸭子类型,当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子
。
在鸭子类型中,关注点在于对象的行为,能做什么,而不是关注对象所属的类型。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为”鸭子”的对象,并调用它的”走”和”叫”方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的”走”和”叫”方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的”走”和”叫”方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
class Duck:
def quack(self):
print("这鸭子正在嘎嘎叫")
def feathers(self):
print("这鸭子拥有白色和灰色的羽毛")
class Person:
def quack(self):
print "这人正在模仿鸭子"
def feathers(self):
print "这人在地上拿起1根羽毛然后给其他人看"
def in_the_forest(duck):
duck.quack()
duck.feathers()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
对in_the_forest函数而言,对象是一个鸭子。john对象中也拥有quack
和feathers
方法。鸭子类型关注的是对象的行为而不是对象所属的类型。
多态
多态是同一个行为具有多个不同表现形式或形态的能力。
主人家有一只狗和一只猫,主人向他们发出叫指令时,如果是一只狗,就汪汪叫
,如果是一只猫就喵喵叫
。
非多态演示
class Dog:
pass
class Cat:
pass
def sound(animal):
if isinstance(animal, Dog):
print("汪汪")
elif isinstance(animal, Cat):
print("喵喵")
d = Dog()
c = Cat()
sound(d)
sound(c)
#输出
>>> 汪汪
>>> 喵喵
多态代码
class Dog:
def sound(self):
print("汪汪")
class Cat:
def soundself):
print("喵喵")
d = Dog()
c = Cat()
d.sound()
c.sound()
#输出
>>> 汪汪
>>> 喵喵
无论创建狗的对象或者猫的对象,调用sound方法时,都能够正确的打印。
2.抽象基类
ABC,Abstract Base Class(抽象基类),主要定义了基本类和最基本的抽象方法,可以为子类定义共有的API,不需要具体实现。相当于是Java中的接口或者是抽象类。所有继承抽象基类都必须覆盖其父类的方法,抽象基类无法实例化。
抽象基类 StoneBegin博主 python-抽象基类
抽象基类应用:
1.判断某个对象的类型
假设我们需要判断com对象中是否含有__len__方法
。一种方式用hasattr
,另一种可以用抽象基类
from collections.abc import Sized
class Company:
def __init__(self, employee):
self.employee = employee
def __len__(self):
return len(self.employee)
com = Company(['wali', 'eve'])
print(hasattr(com, "__len__"))
#抽象基类,建议使用这种
print(isinstance(com, Sized))
2.强制某个子类实现某些方法
假设需要实现一个缓存,在定义缓存基类时,要求继承CacheBase
基类的子类必须实现其基类get
、set
方法。
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):
def get(self):
pass
def set(self, key, value):
pass
rc = RedisCache()
执行上面代码,是不会抛异常的,因为子类RedisCache
实现其基类的get、set方法。如果RedisCache
类中未实现get、set方法,则在初始化时就会抛异常。
3.类变量和实例变量
类变量就是定义在类中,但是在函数体之外的变量。通常不使用self.变量名
赋值的变量。类变量通常不作为类的实例变量,类变量对所有实例化的对象是公用的。
实例变量定义在方法中的变量,使用self
绑定到实例上的变量,只对实例起作用
class Circle:
#类变量
radius = 5
def __init__(self, x, y):
#实例变量
self.x = x
self.y = y
访问
访问类变量:在类内部访问可以使用className.类变量
,也可以使用self.类变量
访问,但不建议这么访问。在类方法中可以使用cls.类变量
建议使用这种。
访问实例变量:在类的内部实例方法使用self.实例变量
,在类的外部使用对象.实例变量
的形式访问。
class Circle:
radius = 5
def __init__(self, x, y):
self.x = x
self.y = y
print("类名访问类变量:{}".format(Circle.radius))
print("self访问类变量:{}".format(self.radius))
cir = Circle(10, 20)
# 访问类变量
print("类外部访问类变量:{}".format(Circle.radius))
# 访问实例变量
print("访问x:{}".format(cir.x))
4.python继承顺序
在 Python 3中多继承模式是使用C3
算法来确定 MRO(Method Resolution Order) 的。关于MRO小伙伴们请自行google。
class D:
pass
class C(D):
pass
class B(D):
pass
class A(B, C):
pass
print(A.__mro__)
# 输出
>>> (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
class D:
pass
class E:
pass
class C(E):
pass
class B(D):
pass
class A(B, C):
pass
print(A.__mro__)
# 输出
>>> (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)
5.私有属性
python中的私有属性,小菜这篇文章介绍的比较清楚,这里简单的做个补充
class Person:
def __init__(self, name):
self.__name = name
class Student(Person):
def __init__(self, name):
self.__name = name
tom = Person("Tom")
jerry = Student("Jerry")
print(tom.__dict__)
>>> {'_Person__name': 'Tom'}
print(jerry.__dict__)
>>> {'_Student__name': 'Jerry'}
当定义一个私有变量时,python中会将私有变量变形为_类名__变量名
。上面看到_Person__name
。这种变形后的形式也解决了类中的继承后变量的命名问题。
6.对象的自省机制
自省是通过一定的机制查询到对象的内部结构
class Person:
name = "user"
class Student(Person):
def __init__(self, school_name):
self.school_name = school_name
user = Student("大学")
print(user.__dict__)
>>> {'school_name':'大学'}
print(user.name)
>>> 大学
print(Person.__dict__)
>>> {'__module__': '__main__', 'name': 'user', '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
user.__dict__
只打印school_name属性,可以访问到name属性。Person中的name不属于user对象。当访问user.name属性时,user中name不存在时,会根据MRO算法会上查找,然后在Person中查找name属性。
python中dir()
函数比__dict__
功能更为强大,它能例举出所有的属性。
7.super函数
class A:
def __init__(self):
print("父类")
class B(A):
def __init__(self):
print("子类")
super().__init()
b = B()
>>> 父类
>>> 子类
我们知道super是调用父类的,这种说法有点不严谨。为什么说这种说法不够严谨呢?上面这个例子super就是调用的了A。对于单继承来说没什么毛病,但是对于多继承就不一定了。
class D:
def __init__(self):
print("D")
super().__init__()
class C(D):
def __init__(self):
print("C")
super().__init__()
class B(D):
def __init__(self):
print("B")
super().__init__()
class A(B, C):
def __init__(self):
print("A")
super().__init__()
a = A()
>>> A
>>> B
>>> C
>>> D
上面这个例子就说明,如果B调用父类应该打印的D,而不是C。最后的顺序应该是A->B-D-C。但是真实情况打印的是A->B->C-D。这也说明了super是调用MRO顺序的下一个类的函数。