python-面向对象,运算符重载,类函数与静态函数,多重继承,抽象类

面向对象基本概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Document():
def __init__(self, title, author, context):
print('init function called')
self.title = title
self.author = author
self.__context = context # __ 开头的属性是私有属性

def get_context_length(self):
return len(self.__context)

def intercept_context(self, length):
self.__context = self.__context[:length]

harry_potter_book = Document('Harry Potter', 'J. K. Rowling', '... Forever Do not believe any thing is capable of thinking independently ...')

print(harry_potter_book.title)
print(harry_potter_book.author)
print(harry_potter_book.get_context_length())

harry_potter_book.intercept_context(10)

print(harry_potter_book.get_context_length())

print(harry_potter_book.__context)

########## 输出 ##########

init function called
Harry Potter
J. K. Rowling
77
10

---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-5-b4d048d75003> in <module>()
22 print(harry_potter_book.get_context_length())
23
---> 24 print(harry_potter_book.__context)

AttributeError: 'Document' object has no attribute '__context'
  • 类:一群有着相似性的事物的集合,这里对应 Python 的 class。

  • 对象:集合中的一个事物,这里对应由 class 生成的某一个 object,比如代码中的 -harry_potter_book。

  • 属性:对象的某个静态特征,比如上述代码中的 title、author 和 __context。

  • 函数:对象的某个动态能力,比如上述代码中的 intercept_context () 函数。

注意:如果一个属性以 (注意,此处有两个 _) 开头,我们就默认这个属性是私有属性。私有属性,是指不希望在类的函数之外的地方被访问和修改的属性。所以,你可以看到,title 和 author 能够很自由地被打印出来,但是 print(harry_potter_book.context)会报错。

类的专有方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__len__: 获得长度
__cmp__: 比较运算
__call__: 函数调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__truediv__: 除运算
__mod__: 求余运算
__pow__: 乘方

举例,实现运算符重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b

def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)

def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

## output
# Vector(7,8)

类函数与静态函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Document():

# 类中定义常量
WELCOME_STR = 'Welcome! The context for this book is {}.'

def __init__(self, title, author, context):
print('init function called')
self.title = title
self.author = author
self.__context = context

# 类函数
@classmethod
def create_empty_book(cls, title, author):
return cls(title=title, author=author, context='nothing')

# 成员函数
def get_context_length(self):
return len(self.__context)

# 静态函数
@staticmethod
def get_welcome(context):
return Document.WELCOME_STR.format(context)


empty_book = Document.create_empty_book('What Every Man Thinks About Apart from Sex', 'Professor Sheridan Simove')


print(empty_book.get_context_length())
print(empty_book.get_welcome('indeed nothing'))

########## 输出 ##########

init function called
7
Welcome! The context for this book is indeed nothing.

WELCOME_STR 为类常量,类中使用 self.WELCOME_STR ,或者在类外使用Document.WELCOME_STR来表达这个字符串。

静态函数与类没有什么关联,最明显的特征便是,静态函数的第一参数没有任何特殊性,静态函数中只能访问类中的常量或其他静态函数。一般而言,静态函数可以用来做一些简单独立的任务,既方便测试,也能优化代码结构。

类函数的第一个参数一般为 cls,表示必须传一个类进来,cls是一个持有类本身的对象。类函数最常用的功能是实现不同的init 构造函数,比如上文代码中,我们使用 create_empty_book 类函数,来创造新的书籍对象,其 context 一定为 ‘nothing’。通过类函数,可以实现多个构造函数。

进一步说明类函数与静态函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Date(object):

def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year

@classmethod
def from_string(cls, date_as_string):
day, month, year = map(int, date_as_string.split('-'))
date1 = cls(day, month, year)
return date1

@staticmethod
def is_date_valid(date_as_string):
day, month, year = map(int, date_as_string.split('-'))
return day <= 31 and month <= 12 and year <= 3999

date1 = Date(11,09,2012)
date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')

类函数 Date.from_string 相当于创建了另一个“构造函数”,接受一个字符串。

假设我们有一个日期字符串,我们想以某种方式验证它,逻辑上讲可以绑定到Date类,但不需要实例化就可使用。此时就可以使用静态函数了,静态函数无法访问类的内容,基本上只是一个函数。

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# class Entity(object): 新式类
class Entity():
def __init__(self, object_type):
print('parent class init called')
self.object_type = object_type

def get_context_length(self):
raise Exception('get_context_length not implemented')

def print_title(self):
print(self.title)

class Document(Entity):
def __init__(self, title, author, context):
print('Document class init called')
# super(Document, self).__init__('document') 新式类
Entity.__init__(self, 'document')
self.title = title
self.author = author
self.__context = context

def get_context_length(self):
return len(self.__context)

class Video(Entity):
def __init__(self, title, author, video_length):
print('Video class init called')
# super(Video, self).__init__('video') 新式类
Entity.__init__(self, 'video')
self.title = title
self.author = author
self.__video_length = video_length

def get_context_length(self):
return self.__video_length

harry_potter_book = Document('Harry Potter(Book)', 'J. K. Rowling', '... Forever Do not believe any thing is capable of thinking independently ...')
harry_potter_movie = Video('Harry Potter(Movie)', 'J. K. Rowling', 120)

print(harry_potter_book.object_type)
print(harry_potter_movie.object_type)

harry_potter_book.print_title()
harry_potter_movie.print_title()

print(harry_potter_book.get_context_length())
print(harry_potter_movie.get_context_length())

########## 输出 ##########

# Document class init called
# parent class init called
# Video class init called
# parent class init called
# document
# video
# Harry Potter(Book)
# Harry Potter(Movie)
# 77
# 120

注意:形如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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class A(object):
def __new__(cls, *argv, **kwargs):
print('nA')
return super().__new__(cls)

def __init__(self, a):
print('A')
self.a = a

def comeon(self):
print('A.comeon')


class B(A):
def __new__(cls, *argv, **kwargs):
print('nB')
return super().__new__(cls)

def __init__(self, b):
super(B, self).__init__(b)
print('B')
self.b = b

def comeon(self):
print('B.comeon')


class C(A):
def __new__(cls, *argv, **kwargs):
print('nC')
return super().__new__(cls)

def __init__(self, c):
super(C, self).__init__(c)
print('C')
self.c = c

def comeon(self):
print('C.comeon')


class D(B, C):
def __new__(cls, *argv, **kwargs):
print('nD')
return super().__new__(cls)

def __init__(self, d):
super(D, self).__init__(d)
print('D')
self.d = d


d = D('d')
d.comeon()

############输出###########
nD
nB
nC
nA
A
C
B
D
B.comeon

首先看到: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Foo(object):
X = 1
Y = 2

@staticmethod
def averag(*mixes):
return sum(mixes) / len(mixes)

@staticmethod
def static_method():
return Foo.averag(Foo.X, Foo.Y)

@classmethod
def class_method(cls):
return cls.averag(cls.X, cls.Y)


class Son(Foo):
X = 3
Y = 5

@staticmethod
def averag(*mixes):
return sum(mixes) / 3

p = Son()
print(p.static_method())
print(p.class_method())

############输出###########
# 1.5
# 2.6666666666666665

子类的实例继承了父类的static_method静态方法,调用该方法,还是调用的父类的方法和类属性。
子类的实例继承了父类的class_method类方法,调用该方法,调用的是子类的方法和子类的类属性。

抽象类和抽象函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from abc import ABCMeta, abstractmethod

class Entity(metaclass=ABCMeta):
@abstractmethod
def get_title(self):
pass

@abstractmethod
def set_title(self, title):
pass

class Document(Entity):
def get_title(self):
return self.title

def set_title(self, title):
self.title = title

document = Document()
document.set_title('Harry Potter')
print(document.get_title())

entity = Entity()

########## 输出 ##########

Harry Potter

---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-7-266b2aa47bad> in <module>()
21 print(document.get_title())
22
---> 23 entity = Entity()
24 entity.set_title('Test')

TypeError: Can't instantiate abstract class Entity with abstract methods get_title, set_title

Entity是一个抽象类,抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。同样,抽象函数定义在抽象类之中,子类必须重写该函数才能使用。相应的抽象函数,则是使用装饰器 @abstractmethod 来表示。

抽象类就是这么一种存在,它是一种自上而下的设计风范,你只需要用少量的代码描述清楚要做的事情,定义好接口,然后就可以交给不同开发人员去开发和对接。