班级¶
特别是对于较大的代码库,相当数学的编程方式可能会变得有点复杂。试图缓解这一问题的一个范例是 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\) 矩形的属性定义为
在代码中,这由以下定义描述:
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\) ,对于矩形,它被定义为
遵循相同的主题:
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
。随后,这些属性 length
和 width
该对象的 first_rectangle
可以通过以下方式访问 first_rectangle.length
和 first_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
接受论点 length
和 width
,这对于正方形的定义不是必需的,我们可以简化它。现在它只需要一个论据 side_length
。如果我们像在 Rectangle
类,即作为
def __init__(self, side_length):
self.side_length = side_length
这些方法 Square
继承自 Rectangle
将无法调用,因为它们访问属性 length
和 width
,如果我们采用这个定义,它就不会被定义 __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传递给 length
和 width
。
现在可以按如下方式使用该类:
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\) 作为定义属性。实施area
和perimeter
相应的类。阅读并阅读“面向对象设计中的技能构建”一书,以了解面向对象设计的过程。