{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 编程入门11:Python面向对象\n",
    "你已经知道在Python中“一切皆对象”,每个对象都有特定的类型,现在让我们来尝试创建自己的类型——这需要使用class关键字来定义新的“类”(Class),类是用来生成对象的“模板”,对象则是其所属类的“实例”——以下是在交互模式中自定义Thing类,并调用其默认构造器生成一个Thing类的实例对象(注意:自定义类的命名规范要求单词首字母大写):\n",
    "```\n",
    "In [1]: class Thing:\n",
    "   ...:     \"\"\"最简单的自定义类\"\"\"\n",
    "   ...: \n",
    "\n",
    "In [2]: type(Thing)\n",
    "Out[2]: type\n",
    "\n",
    "In [3]: t = Thing()\n",
    "\n",
    "In [4]: type(t)\n",
    "Out[4]: __main__.Thing\n",
    "```\n",
    "你可以看到,Thing对象属于type类型,是type类的一个实例;t对象属于Thing类型,是Thing类的一个实例——当你在程序中定义自己的类来生成实例对象,就算是“面向对象编程”(Object-Oriented Programming,简称OOP)。面向对象的编程方式使用类来模拟和组织现实世界的事物,可以令程序结构更灵活、条理更清晰。\n",
    "![11_articleOOP.png](https://upload-images.jianshu.io/upload_images/10829283-dcd2df7804ef12fd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n",
    "\n",
    "上面定义的Thing类所生成的实例对象并不能做什么事情,让我们再来创建一个包含了具体子语句的“船”类并生成两个“船”对象:\n",
    "```\n",
    "In [5]: class Ship:\n",
    "   ...:     \"\"\"船类\"\"\"\n",
    "   ...:     def __init__(self, name=None):\n",
    "   ...:         \"\"\"初始化船实例\"\"\"\n",
    "   ...:         self.name = name  # 船名\n",
    "   ...:         self.crew = 0  # 船员人数\n",
    "   ...:     def join(self, number):\n",
    "   ...:         \"\"\"船员加入\"\"\"\n",
    "   ...:         self.crew += number\n",
    "   ...:         return self.crew\n",
    "   ...:     \n",
    "\n",
    "In [6]: s1 = Ship(\"郑和\")\n",
    "\n",
    "In [7]: s1.crew = 200\n",
    "\n",
    "In [8]: s2 = Ship(\"戚继光\")\n",
    "\n",
    "In [9]: s2.join(100)\n",
    "Out[9]: 100\n",
    "\n",
    "In [10]: s2.crew\n",
    "Out[10]: 100\n",
    "```\n",
    "Ship类定义了一个特殊的“初始化”方法```__init__```,这样就能在调用构造器生成实例时加入新的实例“属性”(Property),所谓实例属性就是实例对象的“成员变量”,例如Ship类的实例增加了name和crew属性——从现实概念来理解,任何船都有船名和船员人数这两个数据,但每艘船又有各自的具体数据值。实例属性和实例方法是最常见的两种类成员,Python规定特殊类成员名以两个下划线开始和结束,其他类成员名遵循标准的变量命名规范,注意这里有一个细节概念:作为类成员的```__init__```属于函数,作为实例成员的```__init__```则属于方法,在类中定义函数时约定首个参数为“self”,它会指向所生成的实例对象以便操作其成员,对应的实例方法则无此参数,所以调用Ship构造器时只需传入一个参数(也可以不传入任何参数,因为name指定了默认值)。除了实例属性,你也可以定义新的实例方法,让实例能够做更多的事情——例如“船”类还有一个“船员加入”方法。\n",
    "```\n",
    "In [11]: help(Ship)\n",
    "Help on class Ship in module __main__:\n",
    "\n",
    "class Ship(builtins.object)\n",
    " |  船类\n",
    " |  \n",
    " |  Methods defined here:\n",
    " |  \n",
    " |  __init__(self, name=None)\n",
    " |      初始化船实例\n",
    " |  \n",
    " |  join(self, number)\n",
    " |      船员加入\n",
    " |  \n",
    " |  ----------------------------------------------------------------------\n",
    " |  Data descriptors defined here:\n",
    " |  \n",
    " |  __dict__\n",
    " |      dictionary for instance variables (if defined)\n",
    " |  \n",
    " |  __weakref__\n",
    " |      list of weak references to the object (if defined)\n",
    "\n",
    "\n",
    "In [12]: type(Ship.__init__)\n",
    "Out[12]: function\n",
    "\n",
    "In [13]: type(s2.__init__)\n",
    "Out[13]: method\n",
    "\n",
    "In [14]: s1.__dict__\n",
    "Out[14]: {'crew': 200, 'name': '郑和'}\n",
    "```\n",
    "实例对象之所以拥有不必自定义而默认存在的特殊成员,是因为面向对象编程的一个重要特性“继承”(Inheritance)——使用继承机制能够将复杂的系统有机地组织起来,所有类都是同一个庞大家族的成员——定义类时可以在类名后加括号指定“基类”,新类将成为其“子类”;如果不指定基类,就默认为最基本的“object”类的子类。子类会继承基类的现有成员,子类定义属性和方法时如果与基类成员同名,就会“覆盖”基类成员。例如下面的程序定义了“船”类及其子类“战舰”类:\n",
    "```\n",
    "\"\"\"ship.py 船的家族\"\"\"\n",
    "\n",
    "\n",
    "class Ship:\n",
    "    \"\"\"船类\"\"\"\n",
    "    def __init__(self, name=None):\n",
    "        \"\"\"初始化船实例\"\"\"\n",
    "        self.name = name  # 船名\n",
    "        self.crew = 0  # 船员人数\n",
    "\n",
    "    def join(self, number):\n",
    "        \"\"\"船员加入\"\"\"\n",
    "        self.crew += number\n",
    "        return self.crew\n",
    "\n",
    "\n",
    "class Warship(Ship):\n",
    "    \"\"\"战舰类\"\"\"\n",
    "    def __init__(self, name=None, level=None):\n",
    "        super().__init__(name)  # 先调用基类初始化方法\n",
    "        self.level = level  # 舰级\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    ws1 = Warship(\"蓝色空间\", \"恒星级\")\n",
    "    ws1.join(500)\n",
    "    print(\"{}战舰{}号,现有舰员{}人。\".format(ws1.level, ws1.name, ws1.crew))\n",
    "```\n",
    "你可以注意到Warship类重新定义了```__init__```,这就会覆盖Ship类中的```__init__```,所以先调用基类的```__init__```才能继承到基类定义的实例属性name和crew。\n",
    "\n",
    "接下来的示例是一个简单的计算器:\n",
    "```\n",
    "\"\"\"tkcalc.pyw 简单的计算器\n",
    "\"\"\"\n",
    "import tkinter as tk\n",
    "\n",
    "\n",
    "class Calc(tk.Tk):\n",
    "    \"\"\"计算器窗体类\"\"\"\n",
    "    def __init__(self):\n",
    "        \"\"\"初始化实例\"\"\"\n",
    "        tk.Tk.__init__(self)\n",
    "        self.title(\"计算器\")\n",
    "        self.memory = 0  # 暂存数值\n",
    "        self.create()\n",
    "\n",
    "    def create(self):\n",
    "        \"\"\"创建界面\"\"\"\n",
    "        btn_list = [\"C\", \"M->\", \"->M\", \"/\",\n",
    "                    \"7\", \"8\", \"9\", \"*\",\n",
    "                    \"4\", \"5\", \"6\", \"-\",\n",
    "                    \"1\", \"2\", \"3\", \"+\",\n",
    "                    \"+/-\", \"0\", \".\", \"=\"]\n",
    "        r = 1\n",
    "        c = 0\n",
    "        for b in btn_list:\n",
    "            self.button = tk.Button(self, text=b, width=5,\n",
    "                                    command=(lambda x=b: self.click(x)))\n",
    "            self.button.grid(row=r, column=c, padx=3, pady=6)\n",
    "            c += 1\n",
    "            if c > 3:\n",
    "                c = 0\n",
    "                r += 1\n",
    "        self.entry = tk.Entry(self, width=24, borderwidth=2,\n",
    "                              bg=\"yellow\", font=(\"Consolas\", 12))\n",
    "        self.entry.grid(row=0, column=0, columnspan=4, padx=8, pady=6)\n",
    "\n",
    "    def click(self, key):\n",
    "        \"\"\"响应按钮\"\"\"\n",
    "        if key == \"=\":  # 输出结果\n",
    "            result = eval(self.entry.get())\n",
    "            self.entry.insert(tk.END, \" = \" + str(result))\n",
    "        elif key == \"C\":  # 清空输入框\n",
    "            self.entry.delete(0, tk.END)\n",
    "        elif key == \"->M\":  # 存入数值\n",
    "            self.memory = self.entry.get()\n",
    "            if \"=\" in self.memory:\n",
    "                ix = self.memory.find(\"=\")\n",
    "                self.memory = self.memory[ix + 2:]\n",
    "            self.title(\"M=\" + self.memory)\n",
    "        elif key == \"M->\":  # 取出数值\n",
    "            if self.memory:\n",
    "                self.entry.insert(tk.END, self.memory)\n",
    "        elif key == \"+/-\":  # 正负翻转\n",
    "            if \"=\" in self.entry.get():\n",
    "                self.entry.delete(0, tk.END)\n",
    "            elif self.entry.get()[0] == \"-\":\n",
    "                self.entry.delete(0)\n",
    "            else:\n",
    "                self.entry.insert(0, \"-\")\n",
    "        else:  # 其他键\n",
    "            if \"=\" in self.entry.get():\n",
    "                self.entry.delete(0, tk.END)\n",
    "            self.entry.insert(tk.END, key)\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    Calc().mainloop()\n",
    "```\n",
    "![11_calc.png](https://upload-images.jianshu.io/upload_images/10829283-43263251cd6833e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n",
    "\n",
    "程序窗体继承自tkinter.Tk类,要加部件就定义实例属性,要做事情就使用实例方法,这就是OOP的方式。\n",
    "\n",
    "——编程原来这样……\n",
    "\n",
    "## 编程小提示\n",
    "这次给大家分享的是“Python资源大全中文版”,好用的东西都在其中了……\n",
    "https://github.com/jobbole/awesome-python-cn\n",
    "\n",
    "下一篇:[编程入门12:Python异常处理](12_except.ipynb)"
   ]
  }
 ],
 "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.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}