# 第四章 机器学习

在我们编写的所有程序中，我们已经围绕函数设计了我们的程序，即操作数据的语句块。这称为*面向*过程的编程方式。还有另一种组织程序的方法，即组合数据和功能并将其包装在称为对象的内容中。这称为*面向对象*编程范例。大多数情况下，您可以使用过程编程，但在编写大型程序或遇到更适合此方法的问题时，可以使用面向对象的编程技术。

类和对象是面向对象编程的两个主要方面。一**类**创建一个新的*类型*，其中**对象**是**实例**的类。类比是你可以拥有类型的`int`变量，这些变量转换成说存储整数的变量是变量，它们是类的实例（对象）`int`。

> **静态语言程序员注意事项**
>
> 请注意，即使整数也被视为对象（`int`类）。这与C ++和Java（1.5版之前）不同，其中整数是原始本机类型。
>
> 有关`help(int)`课程的详细信息，请参阅。
>
> C＃和Java 1.5程序员会发现这类似于*装箱和拆箱的*概念。

对象可以使用*属于*对象的普通变量来存储数据。属于对象或类的变量称为**字段**。通过使用*属于*类的函数，对象也可以具有功能。这些函数称为类的**方法**。这个术语很重要，因为它有助于我们区分独立的函数和变量以及属于类或对象的函数和变量。总的来说，字段和方法可以称为该类的**属性**。

字段有两种类型 - 它们可以属于类的每个实例/对象，也可以属于类本身。它们分别称为**实例变量**和**类变量**。

使用`class`关键字创建一个类。该类的字段和方法列在缩进块中。

### 该 `self` <a href="#self" id="self"></a>

类方法与普通函数只有一个特定的区别 - 它们必须有一个额外的名字，必须添加到参数列表的开头，但是当你调用方法时，你**不会**给这个参数赋值，Python会提供它。这个特定的变量引用了对象*本身*，按照惯例，它被赋予了名称`self`。

虽然，您可以为此参数指定任何名称，但*强烈建议*您使用该名称`self`- 任何其他名称肯定是不受欢迎的。使用标准名称有许多优点 - 程序的任何读者都会立即识别它，甚至专业的IDE（集成开发环境）也可以帮助您使用`self`。

> **C ++ / Java / C＃程序员注意事项**
>
> 的`self`在Python等同于`this`用C ++指针和`this`在Java和C＃参考。

您一定想知道Python如何为其提供价值`self`以及为什么您不需要为其提供值。一个例子可以说明这一点。假设你有一个被调用的类，`MyClass`并且调用了这个类的实例`myobject`。当您调用此对象的方法时`myobject.method(arg1, arg2)`，Python会自动将其转换为`MyClass.method(myobject, arg1, arg2)`- 这就是所有特殊`self`内容。

这也意味着如果你有一个不带参数的方法，那么你仍然必须有一个参数 - `self`。

### 类 <a href="#class" id="class"></a>

以下示例中显示了最简单的类（另存为`oop_simplestclass.py`）。

```
class Person:
    pass  # An empty block

p = Person()
print(p)
```

输出：

```
$ python oop_simplestclass.py
<__main__.Person instance at 0x10171f518>
```

**这个怎么运作**

我们使用`class`语句和类的名称创建一个新类。接下来是一个缩进的语句块，它构成了类的主体。在这种情况下，我们有一个空块，使用该`pass`语句表示。

接下来，我们使用类的名称后跟一对括号创建此类的对象/实例。（我们将在下一节中了解[有关实例化的更多信息](https://python.swaroopch.com/oop.html#init)）。对于我们的验证，我们只需打印即可确认变量的类型。它告诉我们模块中有一个`Person`类的实例`__main__`。

请注意，还会打印存储对象的计算机内存的地址。地址在您的计算机上具有不同的值，因为Python可以将对象存储在找到空间的任何位置。

### 方法 <a href="#methods" id="methods"></a>

我们已经讨论过类/对象可以像函数一样使用方法，除了我们有一个额外的`self`变量。我们现在看一个例子（另存为`oop_method.py`）。

```
class Person:
    def say_hi(self):
        print('Hello, how are you?')

p = Person()
p.say_hi()
# The previous 2 lines can also be written as
# Person().say_hi()
```

输出：

```
$ python oop_method.py
Hello, how are you?
```

**这个怎么运作**

在这里，我们看到了`self`实际行动。请注意，该`say_hi`方法不带参数，但仍具有`self`函数定义中的参数。

### 该`__init__`方法 <a href="#init" id="init"></a>

有许多方法名称在Python类中具有特殊意义。我们现在将看到该`__init__`方法的重要性。

`__init__`一旦实例化（即创建）类的对象，就运行该方法。该方法对于您想要对对象进行任何*初始化*（即将初始值传递给对象）很有用。请注意，名称的开头和结尾都有双重下划线。

示例（另存为`oop_init.py`）：

```
class Person:
    def __init__(self, name):
        self.name = name

    def say_hi(self):
        print('Hello, my name is', self.name)

p = Person('Swaroop')
p.say_hi()
# The previous 2 lines can also be written as
# Person('Swaroop').say_hi()
```

输出：

```
$ python oop_init.py
Hello, my name is Swaroop
```

**这个怎么运作**

在这里，我们将`__init__`方法定义为采用参数`name`（与通常一样`self`）。在这里，我们只创建一个名为的新字段`name`。请注意，这些是两个不同的变量，即使它们都被称为“名称”。没有问题，因为虚线表示法`self.name`意味着存在称为“名称”的东西，它是被称为“自我”的对象的一部分而另一个`name`是局部变量。由于我们明确指出了我们所指的名称，因此没有混淆。

在创建类的新实例时`p`，`Person`我们使用类名，然后是括号中的参数：p = Person（'Swaroop'）。

我们没有明确地调用该`__init__`方法。这是这种方法的特殊意义。

现在，我们可以`self.name`在方法中演示的`say_hi`方法中使用该字段。

### 类和对象变量 <a href="#class-obj-vars" id="class-obj-vars"></a>

我们已经讨论了类和对象（即方法）的功能部分，现在让我们了解数据部分。数据部分，即字段，只不过是*绑定*到类和对象的**名称空间**的普通变量。这意味着这些名称仅在这些类和对象的上下文中有效。这就是为什么它们被称为*名称空间*。

有两种类型的*字段* - 类变量和对象变量，它们根据类或对象是否分别*拥有*变量进行分类。

**类变量**是共享的 - 它们可以被该类的所有实例访问。只有一个类变量的副本，当任何一个对象对类变量进行更改时，所有其他实例都会看到该更改。

**对象变量**由类的每个单独对象/实例拥有。在这种情况下，每个对象都有自己的字段副本，即它们不共享，并且在不同的实例中不以任何方式与字段相关。一个例子将使这个易于理解（另存为`oop_objvar.py`）：

```
class Robot:
    """Represents a robot, with a name."""

    # A class variable, counting the number of robots
    population = 0

    def __init__(self, name):
        """Initializes the data."""
        self.name = name
        print("(Initializing {})".format(self.name))

        # When this person is created, the robot
        # adds to the population
        Robot.population += 1

    def die(self):
        """I am dying."""
        print("{} is being destroyed!".format(self.name))

        Robot.population -= 1

        if Robot.population == 0:
            print("{} was the last one.".format(self.name))
        else:
            print("There are still {:d} robots working.".format(
                Robot.population))

    def say_hi(self):
        """Greeting by the robot.

        Yeah, they can do that."""
        print("Greetings, my masters call me {}.".format(self.name))

    @classmethod
    def how_many(cls):
        """Prints the current population."""
        print("We have {:d} robots.".format(cls.population))


droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()

droid2 = Robot("C-3PO")
droid2.say_hi()
Robot.how_many()

print("\nRobots can do some work here.\n")

print("Robots have finished their work. So let's destroy them.")
droid1.die()
droid2.die()

Robot.how_many()
```

输出：

```
$ python oop_objvar.py
(Initializing R2-D2)
Greetings, my masters call me R2-D2.
We have 1 robots.
(Initializing C-3PO)
Greetings, my masters call me C-3PO.
We have 2 robots.

Robots can do some work here.

Robots have finished their work. So let's destroy them.
R2-D2 is being destroyed!
There are still 1 robots working.
C-3PO is being destroyed!
C-3PO was the last one.
We have 0 robots.
```

**这个怎么运作**

这是一个很长的例子，但有助于演示类和对象变量的本质。这里，`population`属于`Robot`类，因此是一个类变量。的`name`变量属于对象（它是使用分配`self`），并且因此是一个对象的变量。

因此，我们将`population`类变量称为`Robot.population`而不是`self.population`。我们在该对象的方法中`name`使用`self.name`符号来引用对象变量。记住类和对象变量之间的这种简单区别。另请注意，与类变量同名的对象变量将隐藏类变量！

而不是`Robot.population`，我们也可以使用，`self.__class__.population`因为每个对象都通过`self.__class__`属性引用它的类。

的`how_many`实际上是属于类，而不是到对象的方法。这意味着我们可以将其定义为a `classmethod`或a，`staticmethod`具体取决于我们是否需要知道我们属于哪个类。由于我们引用了一个类变量，让我们使用`classmethod`。

我们已经`how_many`使用[装饰器](https://python.swaroopch.com/more.html#decorator)将该方法标记为类方法。

装饰器可以被想象为调用包装函数的快捷方式（即一个“包裹”另一个函数的函数，以便它可以在内部函数之前或之后执行某些操作），因此应用`@classmethod`装饰器与调用相同：

```
how_many = classmethod(how_many)
```

注意该`__init__`方法用于使用`Robot`名称初始化实例。在这种方法中，我们将`population`计数增加1，因为我们还增加了一个机器人。还要注意，`self.name`每个对象的值都是特定的，表示对象变量的性质。

请记住，您必须`self` *仅*使用相同对象的变量和方法。这称为*属性引用*。

在这个程序中，我们还看到了对类和方法使用*docstrings*。我们可以在运行时使用`Robot.__doc__`docstring和docstring方法访问类docstring`Robot.say_hi.__doc__`

在该`die`方法中，我们简单地将`Robot.population`计数减少1。

所有班级成员都是公开的。一个例外：如果您使用带有*双下划线前缀的*名称的数据成员，例如`__privatevar`，Python使用名称修改来有效地使其成为私有变量。

因此，遵循的惯例是，仅在类或对象中使用的任何变量应以下划线开头，并且所有其他名称都是公共的，并且可以由其他类/对象使用。请记住，这只是一个约定，并不是由Python强制执行的（双下划线前缀除外）。

> **C ++ / Java / C＃程序员注意事项**
>
> 所有类成员（包括数据成员）都是*公共的*，所有方法都是Python 中的*虚拟*方法。

### 遗产 <a href="#inheritance" id="inheritance"></a>

面向对象编程的一个主要好处是**重用**代码，其中一种方法是通过**继承**机制实现的。可以最好地将继承想象为实现**类**之间的**类型和子类型**关系。

假设您想编写一个程序，该程序必须跟踪大学中的老师和学生。它们有一些共同的特征，如姓名，年龄和地址。他们还具有特定的特征，如薪水，教师课程和假期，以及学生的分数和费用。

您可以为每种类型创建两个独立的类并处理它们，但添加新的共同特征意味着添加到这两个独立的类。这很快变得笨拙。

更好的方法是创建一个公共类`SchoolMember`，然后让教师和学生类*继承*自这个类，即它们将成为这种类型（类）的子类型，然后我们可以为这些子类型添加特定的特征。

这种方法有许多优点。如果我们添加/更改任何功能`SchoolMember`，它也会自动反映在子类型中。例如，您只需将其添加到SchoolMember课程，即可为教师和学生添加新的ID卡字段。但是，子类型的更改不会影响其他子类型。另一个优点是，您可以将教师或学生对象称为`SchoolMember`对象，这在某些情况下可能很有用，例如计算学校成员的数量。这称为**多态**，其中子类型可以在期望父类型的任何情况下被替换，即该对象可以被视为父类的实例。

还要注意我们重用了父类的代码，我们不需要在不同的类中重复它，就像我们使用独立类时一样。

`SchoolMember`这种情况下的类称为**基类**或**超类**。在`Teacher`和`Student`类被称为**派生类**或**子类**。

我们现在将此示例视为一个程序（另存为`oop_subclass.py`）：

```
class SchoolMember:
    '''Represents any school member.'''
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print('(Initialized SchoolMember: {})'.format(self.name))

    def tell(self):
        '''Tell my details.'''
        print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")


class Teacher(SchoolMember):
    '''Represents a teacher.'''
    def __init__(self, name, age, salary):
        SchoolMember.__init__(self, name, age)
        self.salary = salary
        print('(Initialized Teacher: {})'.format(self.name))

    def tell(self):
        SchoolMember.tell(self)
        print('Salary: "{:d}"'.format(self.salary))


class Student(SchoolMember):
    '''Represents a student.'''
    def __init__(self, name, age, marks):
        SchoolMember.__init__(self, name, age)
        self.marks = marks
        print('(Initialized Student: {})'.format(self.name))

    def tell(self):
        SchoolMember.tell(self)
        print('Marks: "{:d}"'.format(self.marks))

t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 25, 75)

# prints a blank line
print()

members = [t, s]
for member in members:
    # Works for both Teachers and Students
    member.tell()
```

输出：

```
$ python oop_subclass.py
(Initialized SchoolMember: Mrs. Shrividya)
(Initialized Teacher: Mrs. Shrividya)
(Initialized SchoolMember: Swaroop)
(Initialized Student: Swaroop)

Name:"Mrs. Shrividya" Age:"40" Salary: "30000"
Name:"Swaroop" Age:"25" Marks: "75"
```

**这个怎么运作**

要使用继承，我们在类定义中的类名后面的元组中指定基类名（例如，`class Teacher(SchoolMember)`）。接下来，我们观察到`__init__`使用`self` 变量显式调用基类的方法， 以便我们可以初始化子类中实例的基类部分。这是remember-因为我们正在定义一个非常重要 `__init__` 的方法`Teacher` 和 `Student` 子类，Python中不会自动调用基类的构造函数 `SchoolMember`，你必须自己显式调用它。

相反，如果我们没有`__init__` 在子类中定义 方法，Python将自动调用基类的构造函数。

虽然我们可以把实例`Teacher`或`Student`实例，我们会`SchoolMember`和访问`tell`的方法，`SchoolMember`只需输入`Teacher.tell`或者`Student.tell`，我们代替另一种定义`tell`在每个子类中的方法（使用`tell`方法`SchoolMember`的一部分）来定制它为子类。因为我们已经这样做了，所以当我们编写`Teacher.tell`Python时，使用该`tell`子类与超类的方法。但是，如果我们`tell`在子类中没有方法，Python会使用`tell`超类中的方法。Python总是首先在实际的子类类型中开始寻找方法，如果它没有找到任何东西，它会开始按照元组中指定的顺序逐个查看子类的基类中的方法（这里我们只是在类定义中有1个基类，但你可以有多个基类。

关于术语的注释 - 如果继承元组中列出了多个类，则称为**多重继承**。

该`end`参数`print`在超类的`tell()`方法中用于打印一行，并允许下一个打印在同一行继续。这是一种在`print`打印`\n`结束时不打印（换行符号）的技巧。

### 摘要 <a href="#summary" id="summary"></a>

我们现在已经探索了类和对象的各个方面以及与之相关的各种术语。我们还看到了面向对象编程的好处和缺陷。Python是高度面向对象的，从长远来看，仔细理解这些概念将对您有所帮助。

接下来，我们将学习如何处理输入/输出以及如何在Python中访问文件。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://python.zhangxiong.net/di-si-zhang-mian-xiang-dui-xiang.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
