{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 理解对象与类:Python 篇" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "在前面大套的历史背景和理论概念解说之后,我们来看看 Python 中对面向对象概念的具体实现,主要结合一些代码例子进行说明。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 类定义与对象创建" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "首先我们来看看类的定义和对应对象的创建:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "class Dog:\n", "\n", " kind = ''\n", "\n", " def __init__(self, name):\n", " self.name = name\n", " \n", " def bark(self):\n", " return 'woof-woof'\n", "\n", "a = Dog('Fido')\n", "b = Dog('Buddy')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "上面这段代码逐行解释如下:\n", "* 第一行是关键字 `class` 打头,代表类定义的开始,后面是类的名字,然后是一个冒号,表示下面缩进的代码段是 `Dog` 类的定义;在 Python 中类本身也是个对象,叫**类对象**(*class object*);\n", " * 你现在能感觉到,Python 的冒号通常都是这个作用,前面看到过的 `if` `else` `for` `while` `def` 都类似;\n", "* 下面是一个在类定义里直接出现的变量 `kind`,这样定义的变量称为**类变量**(*class variable*),这种变量是整个类共有的,所有该类的对象实例共享;\n", "* 下面是一个函数 `__init__()` 的定义,因为这个函数定义在类里,所以通常我们称之为**方法**(*method*):\n", " * 方法的第一参数必定是 `self`,这是个特殊的变量,代表从这个类实例化出来的对象自己;注意这里是类的定义,还没有真正创建出对象,这个 `self` 相当于对未来产生对象的“提前引用”;类定义中的方法在被调用时解释器都会自动传递这个 `self` 进去;\n", " * 作为一种约定俗成的规矩,类似 `__init__()` 用**双下划线**开头和结尾的方法,属于**特殊方法**(*special method*),是 Python 解释器特别定义和使用的;\n", " * `__init__()` 是解释器在实例化一个对象之后自动调用的方法,用来对新创建的对象实例做初始化;\n", " * 方法可以在 `self` 之后带任意的输入参数,这些输入参数由方法自行使用;\n", " * `self.name` 定义了一个**实例变量**(*instance variable*),前面说了 `self` 代表未来被实例化的对象自身,`.` 表示属于对象的,`name` 则是这个实例变量的名字,这个方法用传进来的参数 `name` 来初始化实例变量 `self.name`,要注意这两个 `name` 是不一样的;\n", "* 下面是函数 `bark()` 的定义,这是我们自定义的方法,没有自定义的输入参数(例行的 `self` 参数不算),然后返回 *dog* 的叫声;\n", "* 缩进的类定义部分结束,下面是创建这个类的对象实例的方法,`a = Dog('Fido')`:\n", " * 一个类的实例化,就是用这个类作为模板创建一个实际存在的对象,Python 的语法是把类对象 `Dog` 当做一个函数来使用,`Dog('Fido')` 看上去就是个函数,它的意思是:创建一个 `Dog` 类的**对象实例**(*instance object*),并用以参数 `'Fido'` 调用类的 `__init__()` 方法;\n", " * 解释器会在内存中创建这个对象实例,然后用参数 `'Fido'` 调用类的 `__init__(self, name)` 方法,第一个参数 `self` 解释器会自动放进去(就是刚刚创建好的对象实例),而 `name` 参数就是 `'Fido'`,`__init__()` 方法运行完毕就会把这个对象的 `self.name` 实例变量设为 `'Fido'`;\n", " * 实例化执行完毕,将创建的对象赋给 `a` 变量。\n", "* 创建第二个对象实例 `b`。\n", "\n", "创建出来的两个对象实例就可以使用了:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'canidae'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 类变量可以通过 class object 来访问,并在所有实例变量之间共享\n", "Dog.kind = 'canidae'\n", "a.kind" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'canidae'" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b.kind" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Fido'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 而实例变量是各个对象实例自己拥有,互不影响\n", "a.name" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Buddy'" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b.name" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'woof-woof'" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 当然我们也可以使用我们定义的方法\n", "a.bark()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "如果前面的看上去有点复杂,可以先努力理解和记住下面的几个要点:\n", "* 可以用 `class MyClass:` 的语法来定义类;\n", "* 类定义中直接定义的变量是类变量(*class variable*),为所有对象实例所共享,可用 `MyClass.xxx` 的语法访问;\n", "* 类定义中出现的 `self.xxx` 是实例变量(*instance variable*),是各个对象实例各自的属性(*attribute*),互不影响,对象实例创建后可以用 `obj.xxx` 的语法访问;\n", "* 类定义中通常会定义初始化方法 `__init__(self, param1, param2, ...)`,该方法会在对象实例创建后自动被调用,其参数中 `self` 是固定的,而后面如果有其他参数,需要在创建对象实例时传给类对象,类似这样:`obj = MyClass(param1, param2, ...)`,这两个地方的参数表是对应上的;\n", "* 类定义中定义的函数是对象实例的方法(*method*),可以用 `obj.method()` 的语法调用;所有方法的第一个参数固定为 `self`,其他参数的使用方法与前述 `__init__` 方法一样。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 第二个类" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "有狗就有猫,我们现在可以依葫芦画瓢的定义一个猫类出来:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "class Cat:\n", "\n", " kind = 'felidae'\n", "\n", " def __init__(self, name):\n", " self.name = name\n", " \n", " def mew(self):\n", " return 'meow-meow'\n", "\n", "c = Cat('Garfield')" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'felidae'" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Cat.kind" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Garfield'" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c.name" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'meow-meow'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c.mew()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "和前面的狗类好像没有太大区别,因为从特征上基本一致:无论是猫还是狗,都有一个类变量 `kind`(动物的分类),以及一个实例变量 `name`(动物的名字),然后有个方法来发出叫声。这时候我们可以想到的是,是不是可以将这两个类的共性特征抽取出来,用一个公共的父类来实现呢?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 父类、子类、继承与多态" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "首先我们定义一个比猫和狗都更加抽象的类,准备用作猫和狗的共通父类,这个类把上面说的共性特征都表达出来,想一下应该怎么写,然后看下面的代码:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "class Animal:\n", "\n", " kind = ''\n", "\n", " def __init__(self, name):\n", " self.name = name\n", " \n", " def voice(self):\n", " return '...'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "我们可以看到,`Animal` 类具有类变量 `kind`、实例变量 `self.name` 和一个方法 `voice()`,但是没有给出具体的分类名和叫声是什么。\n", "\n", "下面我们看看怎么继承这个父类来定义子类,在 Python 中要继承一个已存在的类,使用下面这样的语法:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "class Dog(Animal):\n", " \n", " kind = 'canidae'\n", " \n", " def voice(self):\n", " return 'woof-woof'\n", " \n", " bark = voice" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`class` 语句中类名后面可以紧跟一个括号,里面是要继承的父类,这样定义出来的子类就直接拥有了父类的一切,但可以修改,上面的定义就修改了类变量 `kind` 的值,给出了 `voice()` 方法的一个狗类实现(“汪汪汪”),并定义了一个自己独有的方法 `bark`(作为 `voice` 方法的一个别名)。下面的猫类也类似:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "class Cat(Animal):\n", " \n", " kind = 'felidae'\n", " \n", " def voice(self):\n", " return 'meow-meow'\n", " \n", " mew = voice" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "注意这两个子类都没写构造方法 `__init__()`,因为父类的就好用,不需要改。现在我们可以使用这两个子类来实例化一些对象出来:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "a = Animal('キュゥべえ')\n", "c = Cat('Garfield')\n", "d = Dog('Snoopy')" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "キュゥべえ : ...\n", "Garfield : meow-meow\n", "Snoopy : woof-woof\n" ] } ], "source": [ "for animal in [a, c, d]:\n", " print(animal.name, ':', animal.voice())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "仔细看上面的代码,`a` `c` `d` 虽然是不同类的对象实例,但是因为它们都是 `Animal` 或其子类的对象实例,所以拥有 `Animal` 类所定义的标准接口(**继承**),从而可以在一个循环中一视同仁的处理,而且不同类的对象实例自动调用了各自类定义的 `voice()` 方法实现,产生各自正确的输出(**多态**)。\n", "\n", "所以其实也不复杂吧?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 公有和私有?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "和 C++、Java 等面向对象编程语言不一样,Python 并没有私有成员一说,从语法上讲,所有类定义中出现的类变量、对象变量、方法都是公开的。\n", "\n", "但是 Python 同时给出了一个相对宽松的“弱约束”,就是作为一种约定俗成的代码风格,将类定义中所有下划线 `_` 开头的变量和方法作为“内部”变量和方法来看待,也就是说这些变量和方法属于类的内部实现的一部分,随时可能改变,外部最好不要依赖于这些变量和方法。\n", "\n", "所以 Python 虽然没有提供强制受限的“私有成员”,但还是遵循了“接口与实现分离”的思维模式,我们在定义一个类的时候,如果有不希望外部直接使用的变量和方法,可以在其名字前加一个下划线 `_`。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 小结" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 了解在 Python 中如何定义类和创建类的对象实例;\n", "* 了解在 Python 中的继承和多态的实现方法。\n", "\n", "现在可以再读一遍前一章的概念,应该会有不同的感受。" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 }