在上一篇《手把手陪您学Python》40——类的定义、属性与实例化中,我们学习了面向对象编程的基础知识,了解了类的属性以及实例化属性等,今天我们将会主要介绍类的方法以及实例方法。
1、类的方法:
类的方法其实也可以认为是类具有的某种特征,与类的属性不同的是,这种特征除了可以用变量进行描述以外,还可以做更多的“事情”,而这些事情是通过定义函数的方式来实现的。
也就是说,如果类的属性是可以通过实例访问的变量,那么类的方法就是可以通过实例访问的函数。
类中的函数就称为方法,我们之前学习的有关函数的一切都适用于方法。
虽然我们之前也尝试过对函数和方法的区别进行过描述,但也只是为了让初学的我们更好地理解和使用函数与方法,其实两者的界限并不是那么地清晰,而且从英文上的表示来看,两者也都是function。所以,后面我们会逐渐淡化这两者的区别,无论是说函数还是方法,其实指的是一回事。
让我们通过实例看一看,类的方法是如何通过定义函数的方式来实现的,又可以做哪些事情。
我们选取马里奥的三个技能作为马里奥类的方法,包括可以跳跃、可以攻击,还可以吃东西,作为对马里奥类的补充。这时,对于马里奥类的描述就比较完整了:
In [1]: class Mario:
life = 3
cap = "red"
def jump(): # 没有参数
print("Jumping!")
def attack(name): # 一个参数
print("{} attacked an anomy!".format(name))
def eat(name, food): # 多个参数
print("{} ate a {}!".format(name, food))
print("{} became bigger!".format(name))
运行程序我们就完成了马里奥类的完整的定义。
由于函数是可以自定义参数的,所以类的方法也同样可以不需要参数、一个参数或者多个参数。
函数内的语句既是对函数的定义,也是对类方法的定义,也就是这个类可以做的“事情”。
在上面的实例中,每个类方法执行的内容都是打印一段字符串,但是在真正的游戏中,就不仅仅是这么简单的一句指令了,而是一系列更为复杂的程序来实现画面的变化、背景音乐的配合,甚至还要触发墙被顶破以及敌人被压死的效果等等。
如果更进一步地,不是在控制一个动画效果,而是控制一个真实的机器人马里奥,那么这里就是控制各类设备和传感器的指令,进而控制机器人马里奥的行为。
所以说,在类的方法中,通过定义函数的方式,可以做很多的事情,远比类的属性要复杂和强大得多。
2、类方法的引用
引用类方法和我们之前学过的各种方法的引用方式一样,只要把对象名称改为类名称或者实例名称就好了,如果方法中需要参数,就在括号中加上参数。
类名称.类方法(参数1, 参数2, 参数3...)
我们可以引用一下刚刚定义的马里奥的三个方法,如果在定义方法的时候要求输入参数,我们也相应地输入参数即可。
In [2]: Mario.jump()
Out[2]: Jumping!
In [3]: Mario.attack("Mario")
Out[3]: Mario attacked an anomy!
In [4]: Mario.eat("Mario", "red mushroom")
Out[4]: Mario ate a red mushroom!
Mario became bigger!
如果我们还想给马里奥赋予更多的方法,或者希望在现有的方法中扩展新的内容,就像我们之前自定义函数一样,自行添加相应语句就可以了,这里就不继续演示了。
这时,可以回看一下《手把手陪您学Python》39——面向对象中的内容,我们在讲解的过程中使用到了很多的括号,当时说大家可以先不用理会其中的内容,等讲到后面的时候自然就会了解了。包括括号里标记的“对象”、“属性”、“方法”,现在看起来就会对其中的概念非常清晰了。
像洗衣服过程中括号里的内容,实际上就是我们刚刚讲的对洗衣机类和人类的方法的引用。
1、人打开洗衣机门(人.打开洗衣机门)
2、人把衣服放进去(人.把衣服放进去)
3、人关上洗衣机门(人.关上洗衣机门)
4、人启动电源(人.启动电源)
5、洗衣机清洗衣服(洗衣机.清洗衣服)
6、洗衣机甩干衣服(洗衣机.甩干衣服)
7、洗衣机烘干衣服(洗衣机.烘干衣服)
既然已经定义好了类的方法,那么就像实例可以引用类属性一样,实例应该也可以引用同样的类方法,让我们看一下是不是这样:
In [5]: small_mario = Mario()
small_mario.jump()
Out[5]: ---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-23-11dbe964307a> in <module>
1 small_mario = Mario()
----> 2 small_mario.jump()
TypeError: jump() takes 0 positional arguments but 1 was given
In [6]: small_mario.attack("Small mario")
Out[6]: ---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-24-8478b32ee2d8> in <module>
----> 1 small_mario.attack("Small mario")
TypeError: attack() takes 1 positional argument but 2 were given
In [7]: small_mario.eat("Small mario", "red mushroom")
Out[7]: ---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-25-4b4a74507a8b> in <module>
----> 1 small_mario.eat("Small mario", "red mushroom")
TypeError: eat() takes 2 positional arguments but 3 were given
可以看到,虽然我们将马里奥类实例化成了小马里奥,但是在引用类方法的时候都出现了错误,而且错误类型都提示“只有n个位置参数但应该有n+1个位置参数”。
那么为什么会报错,这多出来的1个位置参数又是什么呢?
这是因为实例引用类方法与实例引用类属性有一些不同,虽然实例可以直接引用“类属性”,但是实例是不能够直接“类方法”的,而只能引用“实例方法”。
3、实例方法
实例方法的定义与类方法有所不同。
类方法的定义与我们自定义函数时参数的设置是一样的,可以没有参数,也可以有一个或者多个参数。而在定义实例方法时,每一个方法都默认要有一个“self”参数,而且必须是要作为第一个参数的。这个“self”参数就是报错提示中多出来的那个参数。
这个self代表的就是实例本身。
也就是说,当我们定义实例方法时,实例本身就是实例方法的一个参数,而且默认是第一个参数。当实例调用这个方法时,会自动将实例传入这个self参数中。每个实例方法都会自动将实例传入self参数中,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
self并不是Python的关键字,把他换成其他名称也是可以的,只不过在Python中约定俗成地使用self代表实例本身,所以我们也照常使用就可以了。
让我们用实例方法的定义方式将上面的类方法进行修改,也就是在每一个方法中都在第一个参数的位置增加self参数,就可以得到下面的实例方法。同时,为了验证不使用self名称也不影响调用,在其中一个参数的设置中使用了其他名称加以验证,但仅此一次。
In [8]: class Mario:
life = 3
cap = "red"
def jump(self):
print("Mario is jumping!")
def attack(good, name): # 使用非self名称进行验证
print("Mario attacked an anomy!")
def eat(self, name, food):
print("Mario ate a {}!".format(food))
print("{} became bigger!".format(name))
这时再让实例去引用这些方法,就能得到我们预期的结果了。
In [9]: small_mario = Mario()
Out[9]: small_mario.jump()
Mario is jumping!
In [10]: small_mario.attack("Small mario")
Out[10]: Mario attacked an anomy!
In [11]: small_mario.eat("Small mario", "red mushroom")
Out[11]: Mario ate a red mushroom!
Small mario became bigger!
所以说,一旦一个类被实例化,就可以像使用函数一样使用这个类,函数的参数就是实例。如果用公式和例子描述的话可能会更清晰一些。
实例.实例方法() == 类.实例方法(实例)
In [12]: print(small_mario.jump() == Mario.jump(small_mario))
Out[12]: Mario is jumping!
Mario is jumping!
True
相应地,因为现在定义的是实例方法,如果此时调用类方法的话就会报错了。
In [13]: Mario.jump()
Out[13]: ---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-38-d2cb2ba47b0e> in <module>
----> 1 Mario.jump()
TypeError: jump() missing 1 required positional argument: 'self'
以上就是我们对于类方法以及实例方法的介绍,正是因为有强大的类方法的加持,才让面向对象编程能够精准地模拟现实生活中的情况,并变得无比强大。
下一篇,我们将会继续对类方法的其他内容进行介绍,包括实例属性、魔法方法等,敬请关注。
感谢阅读本文!如有任何问题,欢迎留言,一起交流讨论^_^
要阅读《手把手陪您学Python》系列文章的其他篇目,请关注公众号点击菜单选择,或点击下方链接直达。
《手把手陪您学Python》3——PyCharm的安装和配置
《手把手陪您学Python》5——Jupyter Notebook
For Fans:关注“亦说Python”公众号,回复“手41”,即可免费下载本篇文章所用示例语句。
