面向对象基本概念
1 | class Document(): |
类:一群有着相似性的事物的集合,这里对应 Python 的 class。
对象:集合中的一个事物,这里对应由 class 生成的某一个 object,比如代码中的 -harry_potter_book。
属性:对象的某个静态特征,比如上述代码中的 title、author 和 __context。
函数:对象的某个动态能力,比如上述代码中的 intercept_context () 函数。
注意:如果一个属性以 (注意,此处有两个 _) 开头,我们就默认这个属性是私有属性。私有属性,是指不希望在类的函数之外的地方被访问和修改的属性。所以,你可以看到,title 和 author 能够很自由地被打印出来,但是 print(harry_potter_book.context)会报错。
类的专有方法:
1 | __init__ : 构造函数,在生成对象时调用 |
举例,实现运算符重载:
1 | class Vector: |
类函数与静态函数
1 | class Document(): |
WELCOME_STR 为类常量,类中使用 self.WELCOME_STR ,或者在类外使用Document.WELCOME_STR来表达这个字符串。
静态函数与类没有什么关联,最明显的特征便是,静态函数的第一参数没有任何特殊性,静态函数中只能访问类中的常量或其他静态函数。一般而言,静态函数可以用来做一些简单独立的任务,既方便测试,也能优化代码结构。
类函数的第一个参数一般为 cls,表示必须传一个类进来,cls是一个持有类本身的对象。类函数最常用的功能是实现不同的init 构造函数,比如上文代码中,我们使用 create_empty_book 类函数,来创造新的书籍对象,其 context 一定为 ‘nothing’。通过类函数,可以实现多个构造函数。
进一步说明类函数与静态函数
1 | class Date(object): |
类函数 Date.from_string 相当于创建了另一个“构造函数”,接受一个字符串。
假设我们有一个日期字符串,我们想以某种方式验证它,逻辑上讲可以绑定到Date类,但不需要实例化就可使用。此时就可以使用静态函数了,静态函数无法访问类的内容,基本上只是一个函数。
继承
1 | # class Entity(object): 新式类 |
注意:形如class A(object)为新式类,形如class A()为经典(老式类)定义
首先需要注意的是构造函数。每个类都有构造函数,继承类在生成对象的时候,是不会自动调用父类的构造函数的,因此你必须在 init() 函数中显式调用父类的构造函数。它们的执行顺序是 子类的构造函数 -> 父类的构造函数。
其次需要注意父类 get_context_length() 函数。如果使用 Entity 直接生成对象,调用get_context_length() 函数,就会 raise error 中断程序的执行。这其实是一种很好的写法,叫做函数重写,可以使子类必须重新写一遍 get_context_length() 函数,来覆盖掉原有函数。
最后需要注意到 print_title() 函数,这个函数定义在父类中,但是子类的对象可以毫无阻力地使用它来打印 title,这也就体现了继承的优势:减少重复的代码,降低系统的熵值(即复杂度)。
关于多重继承
在Python里,当你新构造一个对象时,有两个步骤:首先是自底向上,从左至右调用new,然后再依照递归栈依次调用init。这个问题可以用以下代码说明:
1 | class A(object): |
首先看到:d.comeon是从左自右得来的左边的那个B的comeon。那么如何实现这样的效果呢?很简单,让B的init最后一个执行,就能覆盖掉C和D写入的comeon。
所以实际调用new的顺序就是D–B–C–A,之后递归栈回过头来初始化,调用init的顺序就是A–C–B–D,只有这样才能保证B里的comeon能够覆盖掉A的init带入的comeon和C带入的comeon,同样保证如果你的D里有个comeon,它是最后一个init的,将最后写入而覆盖掉其它的。
类函数与静态函数的继承
1 | class Foo(object): |
子类的实例继承了父类的static_method静态方法,调用该方法,还是调用的父类的方法和类属性。
子类的实例继承了父类的class_method类方法,调用该方法,调用的是子类的方法和子类的类属性。
抽象类和抽象函数
1 | from abc import ABCMeta, abstractmethod |
Entity是一个抽象类,抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。同样,抽象函数定义在抽象类之中,子类必须重写该函数才能使用。相应的抽象函数,则是使用装饰器 @abstractmethod 来表示。
抽象类就是这么一种存在,它是一种自上而下的设计风范,你只需要用少量的代码描述清楚要做的事情,定义好接口,然后就可以交给不同开发人员去开发和对接。