对象基础
示例代码:
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
41class 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()函数。
- init 表示构造函数,即在一个对象生成时会被自动调用的函数。我们能看到,
harry_po tter_book=Document(...)
这一行代码被执行的时候,init function called
字符串会被打印出来。 - 而 get_context length()和intercept_context()则为类的普通函数,我们调用它们来对对象的属性做一些事情。
- class Document 还有三个属性,title、author和_context分别表示标题、作者和内容,通过构造函数传入。这里代码很直观,我们可以看到,intercept_context 能修改对象harry_potter_book的_context 属性。
- 另外注意的一点是,如果一个属性以
__
(注意,此处有两个_)开头,我们就默认这个属性是私有属性。私有属性,是指不希望在类的函数之外的地方被访问和修改的属性。所以,你可以看到,title和author 能够很自由地被打印出来,但是print(harry potter_book.__context)就会报错。
进阶
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
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.
- 如何在一个类中定义一些常量,每个对象都可以方便访问这些常量而不用重新构造?:
- 在Python的类里,只需要和函数并列地声明并赋值,就可以实现这一点,例如这段代码中的WELCOME_STR。
- 一种很常规的做法,是用全大写来表示常量,因此我们可以在类中使用self.WELCOME_STR,或者在类外使用Entity.WELCOME_STR,来表达这个字符
- 如果一个函数不涉及到访问修改这个类的属性,而放到类外面有点不恰当,怎么做才能更优雅呢?
- 针对这个问题,我们提出了类函数、成员函数和静态函数三个概念。
- 前两者产生的影响是动态的,能够访问或者修改对象的属性;
- 而静态函数则与类没有什么关联,最明显的特征便是,静态函数的第一个参数没有任何特殊性。
- 一般而言,静态函数可以用来做一些简单独立的任务,既方便测试,也能优化代码结构。静态函数还可以通过在函数前一行加上@staticmethod来表示,代码中也有相应的示例。这其实使用了装饰器的概念,我们会在后面的章节中详细讲解。
- 而类函数的第一个参数一般为cls,表示必须传一个类进来。类函数最常用的功能是实现不同的init 构造函数,比如上文代码中,我们使用create_empty_book类函数,来创造新的书籍对象,其context一定为’nothing’。这样的代码,就比你直接构造要清晰一些。类似的,类函数需要装饰器@classmethod 来声明。
- 成员函数则是我们最正常的类的函数,它不需要任何装饰器声明,第一个参数self代表当前对象的引用,可以通过此函数,来实现想要的查询/修改类的属性等功能。
继承
既然类是一群相似的对象的集合,那么可不可以是一群相似的类的集合呢?这就涉及到类的继承
类的继承,指的是一个类既拥有另一个类的特征,也拥有不同于另一个类的独特特征。在这里的第一个类叫做子类,另一个叫做父类,特征其实就是类的属性和函数。
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
57class 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')
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')
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在上面的代码中,Document和Video它们有相似的地方,都有相应的标题、作者和内容等属性。我们可以从中抽象出一个叫做Entity的类,来作为它俩的父类。
- 首先需要注意的是构造函数。每个类都有构造函数,继承类在生成对象的时候,是不会自动调用父类的构造函数的,因此你必须在init()函数中显式调用父类的构造函数。它们的执行顺序是子类的构造函数->父类的构造函数。
- 其次需要注意父类get_context_length()函数。如果使用Entity 直接生成对象,调用get_context_length()函数,就会raise error中断程序的执行。这其实是一种很好的写法,叫做函数重写,可以使子类必须重新写一遍 get_context_length()函数,来覆盖掉原有函数。
- 最后需要注意到print title()函数,这个函数定义在父类中,但是子类的对象可以毫无阻力地使用它来打印title,这也就体现了继承的优势:减少重复的代码,降低系统的嫡值(即复杂度)。
抽象函数和抽象类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
37from 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本身是没有什么用的,只需拿来定义Document和Video的一些基本元素就够了。不过,万一你不小心生成Entity的对象该怎么办呢?为了防止这样的手误,就引入了抽象类的概念。
- 抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。同样,抽象函数定义在抽象类之中,子类必须重写该函数才能使用。相应的抽象函数,则是使用装饰器@abstractmethod来表示。
- 在上述代码中,entity=Entity()直接报错,只有通过Document 继承Entity才能正常使用。
- 这其实正是软件工程中一个很重要的概念,定义接口。大型工程往往需要很多人合作开发,比如在Facebook中,在idea提出之后,开发组和产品组首先会召开产品设计会,PM(Product Manager,产品经理)写出产品需求文档,然后迭代;TL(Team Leader,项目经理)编写开发文档,开发文档中会定义不同模块的大致功能和接口、每个模块之间如何协作、单元测试和集成测试、线上灰度测试、监测和日志等等一系列开发流程。
Reference:
极客时间