Classes

尤其是对于较大的代码基,编程的数学方法可能会变得稍微复杂。试图缓解这种情况的范例是 object-oriented programming .让我们直接讨论一下矩形类的定义。每个矩形都有一个长度和一个宽度,从中可以计算出它的面积和周长。定义如下:

class Rectangle:
    """A rectangle that is described by its length and width."""

    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        """Return the area of the rectangle."""
        return self.length*self.width

    def perimeter(self):
        """Return the perimeter of the rectangle."""
        return 2*(self.length + self.width)

让我们一步一步地来看看这个。类的定义以关键字开头 class 后面是类的名称。在类中可以定义 方法 .它们就像附加到类中的函数。有点奇怪的是 self 作为他们的第一个论点,我们将在一秒钟内达到这个目的。第一种方法是 __init__ .

def __init__(self, length, width):
    self.length = length
    self.width = width

As you use classes you instantiate objects of that class. During the instantiation you can provide some initial arguments to the class to customize the resulting object using the special __init__ method. In this example a rectangle object can be initialized with values for its length \(l\) and for its width \(w\). These values are then stored in the instance as attributes. The reference to the instance is the self argument, that each method has as its first argument. So by attaching information to self, each distinct object has its own state.

现在我们可以在其他方法中利用它。区域 \(A\) 矩形的定义为

\[A=L \CDOT B\]

在代码中,这由以下定义描述:

def area(self):
    """Return the area of the rectangle."""
    return self.length*self.width

如果定义了一个方法,它将占用矩形的长度 self.length ,乘以 self.width 然后返回。如果我们不使用 self.length 但只是 length 或使用 width 而不是 self.width 我们不会在这个矩形初始化期间访问它存储的值,而是访问一些全局值。为了确保我们只使用特定矩形的属性,我们从 self .

获取周长的方法 \(P\) ,对于矩形,其定义为

\[P=2(L+B)\]

遵循相同的主题:

def perimeter(self):
    """Return the perimeter of the rectangle."""
    return 2*(self.length + self.width)

下面将显示此类的用法。

first_rectangle = Rectangle(length=2, width=3)
print('Information about the first rectangle')
print('Length:', first_rectangle.length)
print('Width:', first_rectangle.width)
print('Area:', first_rectangle.area())
print('Perimeter:', first_rectangle.perimeter())

首先是类的对象 Rectangle 是用 length 参数设置为 2 以及 width 参数设置为 3 .我们的第一个名字 Rectangle 对象是 first_rectangle .随后属性 lengthwidth 对象的 first_rectangle 可通过访问 first_rectangle.lengthfirst_rectangle.width ,分别。有人可以说 self 在类定义中,现在替换为对象的名称。在方法的情况下, self 通过从对象调用方法隐式提供参数。所以就足够了 first_rectangle.area() 而不是 first_rectangle.area(self)first_rectangle.area(first_rectangle) ——这两个都是错的。上述代码的输出为

Information about the first rectangle
Length: 2
Width: 3
Area: 6
Perimeter: 10

如果用不同的值实例化另一个矩形,则信息会相应地更改。所以在一个 second_rectangle

second_rectangle = Rectangle(length=5, width=7)
print('Information about the second rectangle')
print('Length:', second_rectangle.length)
print('Width:', second_rectangle.width)
print('Area:', second_rectangle.area())
print('Perimeter:', second_rectangle.perimeter())

输出将是

Information about the second rectangle
Length: 5
Width: 7
Area: 35
Perimeter: 24

遗传

正方形是矩形的一种特殊情况,即边相等的矩形。由于几何性质的计算保持不变,初始化一个正方形的一个选项可以是

square = Rectangle(length=2, width=2)

但也许你想在初始化方块时更明确一些。这就是继承开始的地方。让我们来看看 Square 从以前定义的 Rectangle 班级:

class Square(Rectangle):
    """A square that is described by its side length."""

    def __init__(self, side_length):
        super().__init__(length=side_length, width=side_length)

而不是 Rectangle 类的名称 Square 类后面是包含 Rectangle .这告诉python Rectangle 类是 超类 属于 Square ,即 Square 继承自 Rectangle .继承是指,如果没有另外定义, Square 与它的超类具有完全相同的方法定义 Rectangle .

但是作为 __init__ 方法 Rectangle 接受参数 lengthwidth 这对于一个正方形的定义是不需要的,我们可以简化它。现在只需要一个论点 side_length .如果我们像在 Rectangle 类,即

def __init__(self, side_length):
    self.side_length = side_length

方法 Square 继承自 Rectangle 将无法调用,因为它们访问属性 lengthwidth ,如果我们采用 __init__ 方法。相反,我们可以这样做:

def __init__(self, side_length):
    self.length = side_length
    self.width = side_length

所以现在的定义看起来非常类似于 Rectangle 类。可能有点太相似了,我们不想再重复一遍。另一个副作用是, __init__ 方法 Rectangle 实现更多的代码,必须将其复制到 Square 也。由于这很容易出错,所以有一种方法可以在子类中利用超类的方法,并使用 super() 功能。如果在方法定义中使用,然后调用方法,它将解析为实现具有此名称的方法的第一个父类,并为当前对象调用它。因此,通过

def __init__(self, side_length):
    super().__init__(length=side_length, width=side_length)

我们告诉 Python __init__ 超类方法 Square 把边长传给 lengthwidth .

现在可以这样使用类:

first_square = Square(side_length=2)
print('Information about the first square')
print('Length:', first_square.length)
print('Width:', first_square.width)
print('Area:', first_square.area())
print('Perimeter:', first_square.perimeter())

各自的输出:

Information about the first square
Length: 2
Width: 2
Area: 4
Perimeter: 8

类型检查

通过使用 isinstance() 功能。第一个参数是应检查其类型的对象,第二个参数是要检查的类。

def what_is_it(object):
    if isinstance(object, Rectangle):
        print('It is a rectangle.')
    if isinstance(object, Square):
        print('It is a square.')

如果我们在几何物体上使用这个简单的函数,你就能看到它是如何工作的。

what_is_it(first_rectangle)

AS first_rectangle 是一个 Rectangle ,但不是 Square ,输出为:

It is a rectangle.

现在为 first_square

what_is_it(first_square)

AS SquareRectangle 您还可以看到它除了作为 Square .

It is a rectangle.
It is a square.

特殊方法

python类的一些行为是通过所谓的 Special method names .下面是如何使用它们的示例:

import math

class FreeVector:
    """A vector that is not bound by an initial or terminal point."""

    def __init__(self, vector):
        self.vector = tuple(vector)

    @property
    def magnitude(self):
        return math.sqrt(math.fsum(v**2 for v in self.vector))

    @property
    def direction(self):
        magnitude = self.magnitude
        return tuple(v/magnitude for v in self.vector)

    def __repr__(self):
        return '{self.__class__.__name__}(vector={self.vector!r})'.format(
            self=self)

    def __str__(self):
        return str(self.vector)

    def __eq__(self, other):
        if (isinstance(other, FreeVector) and
                all(math.isclose(a, b) for a, b in zip(
                    other.vector, self.vector))):
            return True
        else:
            return False

    def __neq__(self, other):
        return not self.__eq__(self, other)

    def __add__(self, other):
        if not isinstance(other, FreeVector):
            return NotImplemented
        return tuple(a + b for a, b in zip(self.vector, other.vector))

    def __sub__(self, other):
        if not isinstance(other, FreeVector):
            return NotImplemented
        return tuple(a - b for a, b in zip(self.vector, other.vector))

用法如下:

>>> a = FreeVector((1, 2, 3))
>>> a
FreeVector(vector=(1, 2, 3))
>>> str(a)
'(1, 2, 3)'
>>> b = FreeVector((1, 2, 3))
>>> c = FreeVector((4, 5, 6))
>>> a == b
True
>>> a == c
False
>>> a + c
(5, 7, 9)
>>> c - a
(3, 3, 3)

练习

  • 复制的定义 Rectangle 类并通过添加方法扩展它 aspect_ratio 返回其长度与宽度之比。

  • 定义一个 Circle 用半径分类 \(r\) 作为定义属性。实施 areaperimeter 相应地分类。

  • 阅读并学习《面向对象设计中的构建技巧》一书,了解面向对象设计的过程。