本小节介绍了鸭子模型、抽象基类、类变量和实例变量、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对象中也拥有quackfeathers方法。鸭子类型关注的是对象的行为而不是对象所属的类型。

多态

多态是同一个行为具有多个不同表现形式或形态的能力。

主人家有一只狗和一只猫,主人向他们发出叫指令时,如果是一只狗,就汪汪叫,如果是一只猫就喵喵叫

非多态演示

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基类的子类必须实现其基类getset方法。

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。

ssl

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'>)

ssl

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。对于单继承来说没什么毛病,但是对于多继承就不一定了。

ssl

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顺序的下一个类的函数。

python3.7 进阶

python 一切皆对象 python 魔法函数 python 类和对象 python Mixin python 自定义序列类 python dict python 对象引用、可变性和垃圾回收 python 元类编程