学python第八课-面向对象编程基础

学python第八课-面向对象编程基础

前言


我只是个学python拖了好久的小萌新,一直要学一直要学,刚来注册说到现在,终于开始了。。

这几个月一直在找适合自己的教程,看过几百本电子书,看过无数新老视频教程,最后还是决定用 骆昊大大 的:Python - 100天从新手到大师(zzzain46大大友情推荐)

练习以外的代码应该没有我写的….

软件方面,没有用PyCharm,在用VS Code

python 版本:3.8.1 操作系统:Windows 10

要牢记的话


感谢:https://github.com/jackfrued/Python-100-Days

  • Make English as your working language. (让英语成为你的工作语言
  • Practice makes perfect. (熟能生巧
  • All experience comes from mistakes. (所有的经验都源于你犯过的错误
  • Don’t be one of the leeches. (不要当伸手党
  • Either outstanding or out. (要么出众要么出局

大纲

Day08 - 面向对象编程基础

  • 类和对象 - 什么是类 / 什么是对象 / 面向对象其他相关概念
  • 定义类 - 基本结构 / 属性和方法 / 构造器 / 析构器 / __ str__方法
  • 使用对象 - 创建对象 / 给对象发消息
  • 面向对象的四大支柱 - 抽象 / 封装 / 继承 / 多态
  • 基础练习 - 定义学生类 / 定义时钟类 / 定义图形类 / 定义汽车类

面向对象编程基础

面向对象编程基础

活在当下的程序员应该都听过”面向对象编程”一词,也经常有人问能不能用一句话解释下什么是”面向对象编程”,我们先来看看比较正式的说法。

“把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。”

这样一说是不是更不明白了。所以我们还是看看更通俗易懂的说法,下面这段内容来自于知乎

img

说明: 以上的内容来自于网络,不代表作者本人的观点和看法,与作者本人立场无关,相关责任不由作者承担。

之前我们说过”程序是指令的集合“,我们在程序中书写的语句在执行时会变成一条或多条指令然后由CPU去执行。当然为了简化程序的设计,我们引入了函数的概念,把相对独立且经常重复使用的代码放置到函数中,在需要使用这些功能的时候只要调用函数即可;如果一个函数的功能过于复杂和臃肿,我们又可以进一步将函数继续切分为子函数来降低系统的复杂性。但是说了这么多,不知道大家是否发现,所谓编程就是程序员按照计算机的工作方式控制计算机完成各种任务。但是,计算机的工作方式与正常人类的思维模式是不同的,如果编程就必须得抛弃人类正常的思维方式去迎合计算机,编程的乐趣就少了很多,”每个人都应该学习编程”这样的豪言壮语就只能说说而已。当然,这些还不是最重要的,最重要的是当我们需要开发一个复杂的系统时,代码的复杂性会让开发和维护工作都变得举步维艰,所以在上世纪60年代末期,”软件危机“、”软件工程“等一系列的概念开始在行业中出现。

当然,程序员圈子内的人都知道,现实中并没有解决上面所说的这些问题的”银弹“,真正让软件开发者看到希望的是上世纪70年代诞生的Smalltalk编程语言中引入的面向对象的编程思想(面向对象编程的雏形可以追溯到更早期的Simula语言)。按照这种编程理念,程序中的数据和操作数据的函数是一个逻辑上的整体,我们称之为“对象”,而我们解决问题的方式就是创建出需要的对象并向对象发出各种各样的消息,多个对象的协同工作最终可以让我们构造出复杂的系统来解决现实中的问题。

说明: 当然面向对象也不是解决软件开发中所有问题的最后的“银弹”,所以今天的高级程序设计语言几乎都提供了对多种编程范式的支持,Python也不例外。

类和对象

简单的说,类是对象的蓝图和模板,而对象是类的实例。这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。

img

定义类

在Python中可以使用class关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下所示。

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

# __init__是一个特殊方法用于在创建对象时进行初始化操作
# 通过这个方法我们可以为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age

def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))

# PEP 8要求标识符的名字用全小写多个单词用下划线连接
# 但是部分程序员和公司更倾向于使用驼峰命名法(驼峰标识)
def watch_movie(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
print('%s正在观看岛国爱情大电影.' % self.name)

说明: 写在类中的函数,我们通常称之为(对象的)方法,这些方法就是对象可以接收的消息。

创建和使用对象

当我们定义好一个类之后,可以通过下面的方式来创建对象并给对象发消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def main():
# 创建学生对象并指定姓名和年龄
stu1 = Student('骆昊', 38)
# 给对象发study消息
stu1.study('Python程序设计')
# 给对象发watch_av消息
stu1.watch_movie()
stu2 = Student('王大锤', 15)
stu2.study('思想品德')
stu2.watch_movie()


if __name__ == '__main__':
main()

访问可见性问题

对于上面的代码,有C++、Java、C#等编程经验的程序员可能会问,我们给Student对象绑定的nameage属性到底具有怎样的访问权限(也称为可见性)。因为在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头,下面的代码可以验证这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Test:

def __init__(self, foo):
self.__foo = foo

def __bar(self):
print(self.__foo)
print('__bar')


def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)


if __name__ == "__main__":
main()

但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。之所以这样设定,可以用这样一句名言加以解释,就是”We are all consenting adults here“。因为绝大多数程序员都认为开放比封闭要好,而且程序员要自己为自己的行为负责。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Test:

def __init__(self, foo):
self.__foo = foo

def __bar(self):
print(self.__foo)
print('__bar')


def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)


if __name__ == "__main__":
main()

在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻,关于这一点可以看看我的《Python - 那些年我们踩过的那些坑》文章中的讲解。

面向对象的支柱

面向对象有三大支柱:封装、继承和多态。后面两个概念在下一个章节中进行详细的说明,这里我们先说一下什么是封装。我自己对封装的理解是”隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口”。我们在类中定义的方法其实就是把数据和对数据的操作封装起来了,在我们创建了对象之后,只需要给对象发送一个消息(调用方法)就可以执行方法中的代码,也就是说我们只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。

练习1:定义一个类描述数字时钟。

初始成品

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
"""
第八课-练习1-定义一个类描述数字时钟。

版本:0.1
作者:alittlebear
"""

#先引进模块,第一个是延迟变量,第二个是清屏用的
from time import sleep
from os import system

#定义对象,名字为Clock
class Clock:

#初始化操作,绑定时分秒为self
def __init__(self, hour, minute, second):
self.hour = hour
self.minute = minute
self.second = second

#显示当前时间
def show(self):
print('现在是%d时%d分%d秒' % (self.hour, self.minute, self.second))

#走一秒,并且运算,满60秒进1分钟,满60分钟进1小时,满24小时清零(因为暂时没搞出天哈哈)
def run(self):
#加一秒
self.second += 1

#判断,满60秒进1分钟,满60分钟进1小时,满24小时清零
if self.second == 60:
self.second = 0
self.minute += 1
if self.minute == 60:
self.minute = 0
self.hour += 1
if self.hour == 24:
self.hour = 0

def main():
#定义对象,clock为23时59分55秒
clock = Clock(23, 59, 55)

#持续循环,+1秒,+1秒。。。
while True:
clock.show()
sleep(1)
clock.run()
system('cls')

if __name__ == '__main__':
main()

参考答案

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
from time import sleep


class Clock(object):
"""数字时钟"""

def __init__(self, hour=0, minute=0, second=0):
"""初始化方法

:param hour: 时
:param minute: 分
:param second: 秒
"""
self._hour = hour
self._minute = minute
self._second = second

def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0

def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)


def main():
clock = Clock(23, 59, 58)
while True:
print(clock.show())
sleep(1)
clock.run()


if __name__ == '__main__':
main()

我的不足+小结

  1. class Clock(object) 为啥要加一个(object)
  2. 为啥大佬喜欢用””” “””来注释,而不是直接#
  3. 运算if我感觉应该嵌套的。。。当初也这么想过,但是感觉没必要(现在想想,感觉数量多的话能减少不少后台运算)
  4. 我都是习惯在def直接print的,没想过return,然后外面print,感觉是个坏习惯。。
  5. hour=0, minute=0, second=0可以设定默认值的。

改进后的成品

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
"""
第八课-练习1-定义一个类描述数字时钟。

版本:0.2
作者:alittlebear
"""

#先引进模块,第一个是延迟变量,第二个是清屏用的
from time import sleep
from os import system

#定义对象,名字为Clock
class Clock:

#初始化操作,绑定时分秒为self
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second

#显示当前时间
def show(self):
return '现在是%d时%d分%d秒' % (self.hour, self.minute, self.second)

#走一秒,并且运算,满60秒进1分钟,满60分钟进1小时,满24小时清零(因为暂时没搞出天哈哈)
def run(self):
#加一秒
self.second += 1

#判断,满60秒进1分钟,满60分钟进1小时,满24小时清零
if self.second == 60:
self.second = 0
self.minute += 1
if self.minute == 60:
self.minute = 0
self.hour += 1
if self.hour == 24:
self.hour = 0

def main():
#定义对象,clock为23时59分55秒
clock = Clock(23, 59, 55)

#持续循环,+1秒,+1秒。。。
while True:
print(clock.show())
sleep(1)
clock.run()
system('cls')

if __name__ == '__main__':
main()

练习2:定义一个类描述平面上的点并提供移动点和计算到另一个点距离的方法。

初始成品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
第八课-练习2-定义一个类描述平面上的点并提供移动点和计算到另一个点距离的方法。

版本:0.1
作者:alittlebear
"""
class Distance:

def __init__(self, x1=0, y1=0, x2=0, y2=0):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2

def calc(self):
return ((self.x2 - self.x1) ** 2 + (self.y2 - self.y1) ** 2) ** 0.5

def main():
distance = Distance(0,0,5,5)
print('距离是:%.2f' % (distance.calc()))

if __name__ == '__main__':
main()

参考答案

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
from math import sqrt


class Point(object):

def __init__(self, x=0, y=0):
"""初始化方法

:param x: 横坐标
:param y: 纵坐标
"""
self.x = x
self.y = y

def move_to(self, x, y):
"""移动到指定位置

:param x: 新的横坐标
"param y: 新的纵坐标
"""
self.x = x
self.y = y

def move_by(self, dx, dy):
"""移动指定的增量

:param dx: 横坐标的增量
"param dy: 纵坐标的增量
"""
self.x += dx
self.y += dy

def distance_to(self, other):
"""计算与另一个点的距离

:param other: 另一个点
"""
dx = self.x - other.x
dy = self.y - other.y
return sqrt(dx ** 2 + dy ** 2)

def __str__(self):
return '(%s, %s)' % (str(self.x), str(self.y))


def main():
p1 = Point(3, 5)
p2 = Point()
print(p1)
print(p2)
p2.move_by(-1, 2)
print(p2)
print(p1.distance_to(p2))


if __name__ == '__main__':
main()

说明: 本章中的插图来自于Grady Booch等著作的《面向对象分析与设计》一书,该书是讲解面向对象编程的经典著作,有兴趣的读者可以购买和阅读这本书来了解更多的面向对象的相关知识。

我的不足+小结

  1. 。。。是我审题有误吗。。算了
  2. _ str _这个def感觉作者没用到。。。不知道是干啥的

总结

  1. 了解了面向对象编程的基础
  2. 了解了如何使用class。。。。也就是面向对象的基础用法
  3. 了解了_ _ init _ _ 是啥

所有用到的链接

疑问

  1. 练习中的class Clock(object) 为啥要加一个(object)

    https://blog.csdn.net/yinboxu/article/details/79852772

    https://www.cnblogs.com/Night-Fury/p/10601972.html

    看起来现在python3默认class就有object类了。。。(我也不知道我在说啥hh)

  2. class中的__ str__不知道是啥

    https://www.runoob.com/note/41154

    当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印从在这个方法中return的数据

    还是看不懂

    后添加:_str_是默认返回方法。

问:我学C++完全搞不懂啥是对象,感觉小熊熊讲的很清楚,我还是看不懂,就C语言学的勉勉强强,C++STL都没看懂,更别说啥类和对象了,要是小熊熊能给我指点指点就好了。img

PS:有的时候感觉明白了啥是类和对象,有的时候又糊涂了,我这脑瓜子没小熊熊的好用啊。img

答:我也是一知半解,说一下我的理解,说错了别怪我哈!我也只学了一天而已img

首先不知道您聊不了解python的模块,也就是modules,比如说我要加载time模块,就会这样:

import time

然后怎么用呢?比如说延时:time.sleep(1) #等待一秒

这时候我就感觉time是一个类,sleep是一个对象

再说个清楚点的吧!希望我的想法没错

类,英文名是class,可以叫成:一个班级

然后一个班级有很多学生,可以说成是对象,目标,object

当我叫三年二班李子明的时候,不能直接说:输出李子明,而是要:输出三年二班的李子明

也就是:print(class32.liziming),而不是print(liziming)

评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...