4 道练习题·预计 70 分钟·做对一题解锁下一段
之前的文章都是使用 Sublime Text 来编写 Python 的,主要是为了更好地熟悉和了解 Python;从这章开始,改用 PyCharm 了。前面 集成开发环境(IDE):PyCharm 一节已经介绍过 PyCharm 的安装和使用,需要的话可以回看。
这篇内容非常的重要,也是我用了很多时间写的。基本上把以前写的东西都重新改了一遍。里面的代码都是我一个一个的敲的,图片也是我一个一个制作的。
编程语言中,一般有两种编程思维,面向过程和面向对象。
面向过程,看重的是解决问题的过程。
这好比我们解决日常生活问题差不多,分析解决问题的步骤,然后一步一步的解决。
而面向对象是一种抽象,抽象是指用分类的眼光去看世界的一种方法。
Python 就是一门面向对象的语言,
如果你学过 Java ,就知道 Java 的编程思想就是:万事万物皆对象。Python 也不例外,在解决实际问题的过程中,可以把构成问题事务分解成各个对象。
面向对象都有两个基本的概念,分别是类和对象。
用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
通过类定义的数据结构实例
面向对象的编程语言,也有三大特性,继承,多态和封装性。
即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
例如:一个 Dog 类型的对象派生自 Animal 类,这是模拟"是一个(is-a)"关系(例图,Dog 是一个 Animal )。
它是指对不同类型的变量进行相同的操作,它会根据对象(或类)类型的不同而表现出不同的行为。
“封装”就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体(即类);封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的访问权限来使用类的成员。
如果你是初次接触面向对象的编程语言,看到这里还一脸懵逼,不要紧,这是正常的。下面我们会通过大量的例子逐步了解 Python 的面向对象的知识。
类是什么?
个人认为理解类,最简单的方式就是:类是一个变量和函数的集合。
可以看下下面的这张图。
这张图很好的诠释了类,就是把变量和函数包装在一起。
当然我们包装也不是毫无目的的包装,我们会把同性质的包装在一个类里,这样就方便我们重复使用。
所以学到现在,你会发现很多编程的设计,都是为了我们能偷懒,重复使用。
知道了类是什么样子的,我们接下来就要学习怎么去定义类了。
类定义语法格式如下:
class ClassName():
<statement-1>
.
.
.
<statement-N>可以看到,我们是用 class 语句来自定义一个类的,其实这就好比我们是用 def 语句来定义一个函数一样。
竟然说类是变量和方法的集合包,那么我们来创建一个类。
class ClassA():
var1 = 100
var2 = 0.01
var3 = '两点水'
def fun1():
print('我是 fun1')
def fun2():
print('我是 fun1')
def fun3():
print('我是 fun1')你看,上面我们就定义了一个类,类名叫做 ClassA , 类里面的变量我们称之为属性,那么就是这个类里面有 3 个属性,分别是 var1 , var2 和 var3 。除此之外,类里面还有 3 个类方法 fun1() , fun2() 和 fun3() 。
我们定义了类之后,那么我们怎么调用类里面的属性和方法呢?
直接看下图:
类属性和方法的调用
类.变量类.函数()这里就不文字解释了(注:做图也不容易啊,只有写过技术文章才知道,这系列文章,多耗时)
好了,知道怎么调用之后,我们尝试一下:
class ClassA():
var1 = 100
var2 = 0.01
var3 = '两点水'
def fun1():
print('我是 fun1')
def fun2():
print('我是 fun1')
def fun3():
print('我是 fun1')
print(ClassA.var1)
print(ClassA.var2)
print(ClassA.var3)
ClassA.fun1()
ClassA.fun2()
ClassA.fun3()输出结果如下:
100
0.01
两点水
我是 fun1
我是 fun1
我是 fun1通过上面我们已经会定义类了,那么这里讲一下在同一个类里,类方法如何调用类属性的。
直接看个例子吧:
class ClassA():
var1 = '两点水'
@classmethod
def fun1(cls):
print('我是 fun1' + cls.var1)
ClassA.fun1()输出结果如下:
我是 fun1两点水注意看,在类方法上面多了个 @classmethod ,这是干嘛用的呢?
这是用于声明下面的函数是类函数。其实从名字就很好理解了。
class 就是类,method 就是方法。
那是不是一定需要注明这个呢?
答案是是的。
如果你没使用,是会报错的。
class ClassA():
var1 = '两点水'
def fun1(cls):
print('我是 fun1' + cls.var1)
ClassA.fun1()报错如下:
Traceback (most recent call last):
File "/Users/twowater/dev/python/test/com/twowater/test.py", line 10, in <module>
ClassA.fun1()
TypeError: fun1() missing 1 required positional argument: 'cls'如果没有声明是类方法,方法参数中就没有 cls , 就没法通过 cls 获取到类属性。
因此类方法,想要调用类属性,需要以下步骤:
@classmethod 声明该方法是类方法。只有声明了是类方法,才能使用类属性cls , cls 是 class 的缩写,其实意思就是把这个类作为参数,传给自己,这样就可以使用类属性了。cls.变量名记住喔,无论是 @classmethod 还是 cls ,都是不能省去的。
省了都会报错。
上面我们学习了类方法如何调用类属性,那么类方法如何传参呢?
其实很简单,跟普通的函数一样,直接增加参数就好了。
这个就直接上例子了:
class ClassA():
var1 = '两点水'
@classmethod
def fun1(cls, age): # 定义了个 age 参数
print('我是 fun1' + cls.var1)
print('年龄: ' + str(age))
ClassA.fun1(18) # 多了个参数打印的结果:
我是 fun1两点水
年龄: 18来,我们先来温习一下类的结构。
看着这个结构,提一个问题,如何修改类属性,也就是类里面的变量?
从类结构来看,我们可以猜测,从类方法来修改,也就是从类内部来修改和增加类属性。
看下具体的实例:
class ClassA():
var1 = '两点水'
@classmethod
def fun1(cls):
print('原来的 var1 值为: ' + cls.var1)
cls.var1 = input('请输入修改 var1 的值: ')
print('修改后 var1 值为: ' + cls.var1)
cls.var2 = input('新增类属性 var2 ,请为它赋值为: ')
print('修改后 var2 值为: ' + cls.var2)
ClassA.fun1()输出结果如下(绿色字的为我输入的值):
原来的 var1 值为: 两点水
请输入修改 var1 的值: 三点水
修改后 var1 值为: 三点水
新增类属性 var2 ,请为它赋值为: 四点水
修改后 var2 值为: 四点水这里还是强调一下,例子还是要自己多写,不要只看,自己运行, 看效果。多想。
我们刚刚看了通过类方法来修改类的属性,这时我们看下从外部如何修改和增加类属性。
例子如下:
class ClassA():
var1 = '两点水'
@classmethod
def fun1(cls):
print('var1 值为: ' + cls.var1)
ClassA.fun1()
ClassA.var1 = input('请输入修改 var1 的值: ')
ClassA.fun1()
ClassA.var2 = input('请输入新增属性 var2 的值: ')
print(ClassA.var2)输出结果如下:
var1 值为: 两点水
请输入修改 var1 的值: 三点水
var1 值为: 三点水
请输入新增属性 var2 的值: 四点水
四点水这部分内容主要讲类和对象,我们先来说说类和对象之间的关系。
类是对象的模板
我们得先有了类,才能制作出对象。
类就相对于工厂里面的模具,对象就是根据模具制造出来的产品。
从模具变成产品的过程,我们就称为类的实例化。
类实例化之后,就变成对象了。也就是相当于例子中的产品。
这里强调一下,类的实例化和直接使用类的格式是不一样的。
之前我们就学过,直接使用类格式是这样的:
class ClassA():
var1 = '两点水'
@classmethod
def fun1(cls):
print('var1 值为:' + cls.var1)
ClassA.fun1()而类的实例化是怎样的呢?
是这样的,可以仔细对比一下,类的实例化和直接使用类的格式有什么不同?
class ClassA(object): # 这个可以先不管,是关于继承的,后面会说到
var1 = '两点水'
def fun1(self): # 没有了 @classmethod ,cls 变成了 self
print('var1 值为: ' + self.var1)
# 实例化,并且使用
a = ClassA()
# 实例化之后,使用它里面的方法
a.fun1()
# 或者是使用它里面的属性
print(a.var1)输出的结果:
var1 值为: 两点水
两点水主要的不同点有:
@classmethod 声明了,不用声明他是类方法cls 改为 self实例名 = 类() 的方式实例化对象,为类创建一个实例,然后再使用 实例名.函数() 的方式调用对应的方法 ,使用 实例名.变量名 的方法调用类的属性这里说明一下,类方法的参数为什么 cls 改为 self ?
其实这并不是说一定要写这个,你改为什么字母,什么名字都可以。
不妨试一下:
class ClassA(object):
var1 = '两点水'
def fun1(aaaaaaaa): # self 改为 aaaaaaaa 了
print('var1 值为: ' + aaaaaaaa.var1)
# 实例化
a = ClassA()
# 实例化之后,使用它里面的方法
a.fun1()
# 或者是使用它里面的属性
print(a.var1)输出结果:
var1 值为: 两点水
两点水你看,把 self 改为 aaaaaaaa 还是可以一样运行的。
只不过使用 cls 和 self 是我们的编程习惯,这也是我们的编程规范。
因为 cls 是 class 的缩写,代表这类 , 而 self 代表这对象的意思。
所以啊,这里我们实例化对象的时候,就使用 self 。
而且 self 是所有类方法位于首位、默认的特殊参数。
除此之外,在这里,还要强调一个概念,当你把类实例化之后,里面的属性和方法,就不叫类属性和类方法了,改为叫实例属性和实例方法,也可以叫对象属性和对象方法。
为什么要这样强调呢?
因为一个类是可以创造出多个实例对象出来的。
你看下面的例子:
class ClassA(object):
var1 = '两点水'
def fun1(self):
print('var1 值为: ' + self.var1)
# 实例化
a = ClassA()
# 实例化之后,使用它里面的方法
a.fun1()
# 或者是使用它里面的属性
print(a.var1)
# 实例化
b = ClassA()
# 实例化之后,使用它里面的方法
b.fun1()
# 或者是使用它里面的属性
print(b.var1)输出结果:
var1 值为: 两点水
两点水
var1 值为: 两点水
两点水我不仅能用这个类创建 a 对象,还能创建 b 对象
一个类可以实例化多个对象出来。
根据这个图,我们探究一下实例对象的属性和类属性之间有什么关系呢?
先提出第一个问题,如果类属性改变了,实例属性会不会跟着改变呢?
还是跟以前一样,提出了问题,我们直接用程序来验证就好。
看程序:
class ClassA(object):
var1 = '两点水'
def fun1(self):
print('var1 值为: ' + self.var1)
# 实例化
a = ClassA()
# 或者是使用它里面的属性
print(a.var1)
# 改变类属性
ClassA.var1 = '三点水'
# 重新打印实例化对象 a 的属性
print(a.var1)输出结果:
两点水
三点水从程序运行的结果来看,类属性改变了,实例属性会跟着改变。
这很好理解,因为我们的实例对象就是根据类来复制出来的,类属性改变了,实例对象的属性也会跟着改变。
那么相反,如果实例属性改变了,类属性会改变吗?
答案当然是不能啦。因为每个实例都是单独的个体,不能影响到类的。
具体我们做下实验:
class ClassA(object):
var1 = '两点水'
def fun1(self):
print('var1 值为: ' + self.var1)
# 实例化
a = ClassA()
# 或者是使用它里面的属性
print(a.var1)
# 修改实例化的属性只
a.var1 = '三点水'
# 打印修改后实例化的属性值
print(a.var1)
# 打印类的属性值
print(ClassA.var1)输出结果:
两点水
三点水
两点水可以看到,不管实例对象怎么修改属性值,对类的属性还是没有影响的。
那这里跟上面一样,还是提出同样的问题。
如果类方法改变了,实例方法会不会跟着改变呢?
看下下面的例子:
class ClassA(object):
var1 = '两点水'
def fun1(self):
print('关注十年不更新一次的公众号: ReadingWithU')
# 实例化
a = ClassA()
# 或者是使用它里面的方法
a.fun1()
# 修改类方法
# 1. 先要定义一个新函数
def newFun1(self):
print('加我个人微信号: thinktoday2019 交个好友? ')
# 2. 用新的函数替代老的函数,也就是「重写类方法」
ClassA.fun1 = newFun1
# 再次调用实例的方法
a.fun1()输出结果:
关注十年不更新一次的公众号: ReadingWithU
加我个人微信号: thinktoday2019 交个好友? 这里建议我的例子,各位都要仔细看一下,自己重新敲一遍。相信为什么要这么做,这么证明。
还是那句话多想,多敲。
回归正题,从运行的结果来看,类方法改变了,实例方法也是会跟着改变的。
在这个例子中,我们需要改变类方法,就用到了类的重写。
我们使用了 类.原始函数 = 新函数 就完了类的重写了。
要注意的是,这里的赋值是在替换方法,并不是调用函数。所以是不能加上括号的,也就是 类.原始函数() = 新函数() 这个写法是不对的。
那么如果实例方法改变了,类方法会改变吗?
如果这个问题我们需要验证的话,是不是要重写实例的方法,然后观察结果,看看类方法有没有改变,这样就能得出结果了。
可是我们是不能重写实例方法。
你看,会直接报错。
class ClassA(object):
var1 = '两点水'
def fun1(self):
print('关注十年不更新一次的公众号: ReadingWithU')
# 实例化
a = ClassA()
# 使用它里面的方法
a.fun1()
# 1. 先要定义一个新函数
def newFun1(self):
print('加我个人微信号: thinktoday2019 交个好友? ')
# 2. 实例方法替换新函数
a.fun1 = newFun1
# 再次调用实例的方法
a.fun1()报错如下:
关注十年不更新一次的公众号: ReadingWithU
Traceback (most recent call last):
File "/Users/twowater/dev/python/test/com/twowater/test.py", line 24, in <module>
a.fun1()
TypeError: newFun1() missing 1 required positional argument: 'self'前面讲了类与对象,接着把学习时间做成一个很小的类。各位先找出哪些数据属于每一份记录,哪些说明可以由整个类共用。
class StudyRecord:
category = '学习记录'
def __init__(self, subject):
self.subject = subject
self.minutes = 0
def add(self, minutes):
self.minutes = self.minutes + minutes
@classmethod
def get_category(cls):
return cls.category
python_record = StudyRecord('Python')
reading_record = StudyRecord('读书')
python_record.add(15)
print(python_record.minutes)
print(reading_record.minutes)
print(StudyRecord.get_category())输出:
15
0
学习记录self.minutes 是每个实例自己的属性,所以增加 Python 的记录,不会改变读书的记录。category 是定义在类上的属性,这里由实例共同读取。
类里定义的方法还需要区分类型:这里的 add(self, ...) 是实例方法,接收实例;使用 @classmethod 的 get_category(cls) 才是接收类的类方法。self、cls 是约定名称,普通实例方法、类方法和静态方法也并非都以 self 开头。
自己让两份记录分别增加 5 分钟,应该得到 20 和 5。然后增加一个备注列表,分别试试在 __init__ 中写 self.notes = [],以及在类上写 notes = [],用 append() 观察共享数据的区别。双下划线名称主要涉及名称改写,不是阻止所有外部访问的权限系统,不能把它当成保存秘密的安全边界。
请定义一个类 Animal,类里有一个方法 sound,方法体打印 '某种声音'。
然后:
Animal() 赋给 aa.sound()输出应该是:
某种声音推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai