# 2.4.1 定义

本节目标：

* 掌握如何定义类

## 类的定义

**Learning By Reading 难度：★ 重要性：★★★★★**

* 阅读[材料](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431864715651c99511036d884cf1b399e65ae0d27f7e000)

  ，了解定义类和类中的属性、方法的语法。

## 继承

> \[继承]\([https://zh.wikipedia.org/zh-hans/继承\_(计算机科学))是面向对象的一个基本概念，如果一个类別A「继承自」另一个类別B，就把这个A称为「B的子类別」，而把B称为「A的父类別」也可以称「B是A的超类」。](https://zh.wikipedia.org/zh-hans/%E7%BB%A7%E6%89%BF_\(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6\)\)%E6%98%AF%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%9A%84%E4%B8%80%E4%B8%AA%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%80%E4%B8%AA%E7%B1%BB%E5%88%A5A%E3%80%8C%E7%BB%A7%E6%89%BF%E8%87%AA%E3%80%8D%E5%8F%A6%E4%B8%80%E4%B8%AA%E7%B1%BB%E5%88%A5B%EF%BC%8C%E5%B0%B1%E6%8A%8A%E8%BF%99%E4%B8%AAA%E7%A7%B0%E4%B8%BA%E3%80%8CB%E7%9A%84%E5%AD%90%E7%B1%BB%E5%88%A5%E3%80%8D%EF%BC%8C%E8%80%8C%E6%8A%8AB%E7%A7%B0%E4%B8%BA%E3%80%8CA%E7%9A%84%E7%88%B6%E7%B1%BB%E5%88%A5%E3%80%8D%E4%B9%9F%E5%8F%AF%E4%BB%A5%E7%A7%B0%E3%80%8CB%E6%98%AFA%E7%9A%84%E8%B6%85%E7%B1%BB%E3%80%8D%E3%80%82) 继承可以使得子类別具有父类別的各种属性和方法，而不需要再次编写相同的代码。在令子类別继承父类別的同时，可以重新定义某些属性，并重写某些方法。

比如，“哺乳动物”类继承“动物”类，“人”类又继承“哺乳动物”类。

**Learning By Reading 难度：★ 重要性：★★★★★**

* 阅读材料，了解Python中的[继承](https://www.cnblogs.com/jason-lv/p/8325324.html)，

  以及[方法解析顺序](https://www.cnblogs.com/whatisfantasy/p/6046991.html)。

如果我们定义

```python
class MyTurtle(turtle.Turtle):

    _all_turtles = {}

    def __init__(self, *args, name="", **kwds): # args和kwds主要用来把除了name之外的参数传给turtle.Turtle的__init__，这样我们还是可以MyTurtle('turtle', name='ada')创建出一个小海龟形状的小海龟
        super(MyTurtle).__init__(*args, **kwds) # 很重要！MyTurtle继承了turtle.Turtle，而turtle.Turtle.__init__里面还做了许多其他事情
        self.name = name or "Ishmael" # 给小海龟起名字，如果没有指定名字……管我叫以实玛利吧！
        if self.name in MyTurtle._all_turtles:
            raise Exception("重名了！") # raise语句用来报错，你们曾经看过的各种错误都可以通过raise报出来。
        MyTurtle._all_turtles[name] = self

    def polygon(self, n, side):
        for i in range(n):
            self.fd(side)
            self.rt(360. / n)
```

那么这个`MyTurtle`就是一个继承了`Turtle`的类。 一个`Turtle`对象有的属性和方法，`MyTurtle`对象都有。 除此之外，`MyTurtle`还有一个`polygon`方法，画一个边长为`side`的正`n`边形。

这时候，我们

```python
my_turtle = MyTurtle()
my_turtle.polygon(42)
```

就画出了一个边长为`42`的正方形。

`MyTurtle`类有一个类属性`_all_turtles`用一个`dict`按名字存了所有小海龟。

```python
ada = MyTurtle(name='ada')
print(ada._all_turtles)
```

这样也打印出了所有小海龟。 Python发现对象`ada`没有`_all_turtles`这个属性，于是就会去`ada`所属的类`MyTurtle`去找有没有这个属性。

```python
ada = MyTurtle(name='ada')
ada._all_turtles = 42
print(ada._all_turtles) # 结果：42
print(MyTurtle._all_turtles)
```

对`ada`的属性赋值不会影响类属性。

## 注意类体与函数体的区别

类体中的代码会在定义的过程中执行，而函数体中的代码不会。

```python
def polygon(turtle, n, side):
    print("画一个多边形")
    for i in range(n):
        turtle.fd(side)
        turtle.rt(360. / n)
```

这段代码定义了一个函数`polygon`，但是函数中的`print`在定义的过程中没有被执行。

而

```python
class MyTurtle(turtle.Turtle):
    print("定义继承了Turtle的MyTurtle类")
    def polygon(self, n, side):
        for i in range(n):
            self.fd(side)
            self.rt(360. / n)
```

这段代码定义了一个类，类体中的`print`会被执行。

```python
def _polygon(turtle, n, side):
    for i in range(n):
        turtle.fd(side)
        turtle.rt(360. / n)

class MyTurtle(turtle.Turtle):
    polygon = _polygon
```

这段代码定义了函数`_polygon`，类`MyTurtle`，类中有方法`polygon`。 这时候，

```python
my_turtle = MyTurtle()

my_turtle.polygon(42) # 语句1
MyTurtle.polygon(my_turtle, 42) # 语句2
_polygon(my_turtle, 42) # 语句3
```

语句1、2、3是等价的。

```python
if MyTurtle.polygon == _polygon:
    print("相等！") # 会输出
```

会输出`相等！`。

同样的，

```python
turtle = Turtle()

turtle.fd(42)
Turtle.fd(turtle, 42)
```

两句话也是等价的。

在类体中定义的变量，都会变成类的属性和方法。

但是，在类体定义的变量，无法在外面使用。

比如，

```python
class MyTurtle(turtle.Turtle):
    master = "Your Name" # 类属性，小海龟主人名字
    def polygon(self, n, side):
        for i in range(n):
            self.fd(side)
            self.rt(360. / n)
print(master) # 报错！
print(MyTurtle.master) # 可以！
```

大家可能已经发现，函数中也有类似的情况。 这就牵涉到名称空间和作用域的概念了。
