班级

特别是对于较大的代码库,相当数学的编程方式可能会变得有点复杂。试图缓解这一问题的一个范例是 object-oriented programming 。让我们直接开始讨论Rectangle类的定义。每个矩形都有一个长度和一个宽度,从中可以计算出它的面积和周长。定义如下:

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\cot 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 并将Side_Length传递给 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)

作为 first_rectangle 是一种 Rectangle ,但不是 Square ,输出为:

It is a rectangle.

现在让我们来看一下 first_square

what_is_it(first_square)

作为 Square 是对 Rectangle 您还可以看到,它除了是一个 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 相应的类。

  • 阅读并阅读“面向对象设计中的技能构建”一书,以了解面向对象设计的过程。