Python 是一种多泛型语言,它没有强制程序员使用某种特定的程序设计风格,而是允许程序员采用过程型、函数型或面向对象的程序设计风格,也可以是这些编程风格的有效组合。对于大多数程序而言,尤其对中等规模或大规模的程序,采用面向对象的程序设计风格提供了很多优势。
接下来将基于使用程序对圆进行描述这一问题,来解释纯过程型程序设计方法存在的问题。用于描述一个圆所需要的最少数据包括圆心坐标(x, y)以及圆的半径,简单的方法是使用一个三元组对圆进行描述,比如:circle = (25, 80, 12)
。
如果只给出这个表示,那么我们可以理解为x = 25, y = 80, radius = 12
, 也可以理解为x = 25, radius = 80, y = 12
,二义性是其一个不足之处。另外一个不足在于,只能通过索引位置对其中的值进行存取,还是得提前知道元组中每个位置所代表的含义。
可以使用 namedtuple 解决获取元素顺序的问题(假设distance_from_origin函数已定义):
import collections
Circle = collections.namedtuple('Circle', 'x, y, radius')
circle = Circle(13, 84, 9)
# 假定 distance_from_origin 函数已经定义
distance = distance_from_origin(circle.x, circle.y)
虽然可以解决元素顺序的问题,但是如果半径给定的值为负值,这个数据是不合理的,假设会在 distance_from_origin 函数中进行检查,但这只有在调用的时候才能检查,并不能在创建 Circle 对象的时候进行验证,这就是纯过程型程序设计的弊端。
对象:我们之前见过的 dict、int、str 等数据类型其实是一个类,我们也可以称之为一个 对象。
对象中通常包含属性——方法是可调用的属性,其他属性则是数据。方法其实也是一个函数,只不过其第一个参数是调用该方法的实例本身(self)。在属性名前以两个下划线引导,Python就会阻止无心的访问,因此可以认为是私有的。
面向对象的优势之一是如果我们有一个类,就可以对其进行专用化,这意味着创建一个新类,新类继承原始类的所有属性(数据和方法),通常可以添加或替换原始类中的某些方法,或添加更多的实例变量。
创建自定义类的两种语法:
class className:
suit
class className(base_classes):
suit
我们从简单的类 Point 开始,该类存放坐标 (x, y),定义于文件 Shape.py 中,其完整实现如下:
import math
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def distance_from_origin(self):
return math.hypot(self.x, self.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __repr__(self):
return "Point({0.x!r}, {0.y!r})".format(self)
def __str__(self):
return "({0.x!r}, {0.y!r})".format(self)
Point 类有两个数据属性(self.x和self.y),还包含5个方法(不包括继承来的方法),其中4个属于特殊方法。导入 Shape 模块后, Point 类就可以像其他类一样进行使用,其属性可以直接存取。
01.Point继承关系
对 Point 类的基本使用:
import Shape
a = Shape.Point()
repr(a) # returns: 'Point(0, 0)'
b = Shape.Point(3, 4)
str(b) # returns: '(3, 4)'
b.distance_from_origin() # returns: 5.0
对方法进行调用时,Python 会自动提供第一个参数——这个参数是对对象自身的对象引用(在 C++ 与 Java 中称为 this)。我们必须在参数列表中包含这一参数,根据约定,这一参数称为self。所有的对象属性都必须由self进行限定。
创建一个对象,需要两个步骤:调用特殊方法 __new__() 来创建该对象,之后调用特殊方法 __init__() 对其进行初始化。
接下来我们新建一个继承自 Point 类的 Circle 类,添加一个额外的属性(radius)以及3个新方法,此外重新实现Point类的几个方法:
class Circle(Point):
def __init__(self, radius, x=0, y=0):
super().__init__(x, y)
self.radius = radius
def edge_distance_from_origin(self):
return abs(self.distance_from_origin() - self.radius)
def area(self):
return math.pi * (self.radius ** 2)
def circumference(self):
return 2 * math.pi * self.radius
def __eq__(self, other):
return self.radius == other.radius and self.x == other.x and self.y == other.y
def __repr__(self):
return "Circle({0.radius!r}, {0.x!r}, {0.y!r})".format(self)
def __str__(self):
return repr(self)
Circle 类的继承关系如下:
02.Circle继承关系
在 __init__() 方法中,使用 super() 来调用基类的 __init__() 方法,从而创建并初始化 self.x 属性与 self.y 属性。下面给出两个使用实例:
p = Shape.Point(28, 45)
c = Shape.Circle(5, 28, 45)
p.distance_from_origin() # returns: 53.0
p.distance_from_origin() # returns: 53.0
多态意味着,给定类的任意对象在使用时都可以看做该类的任意某个基类的对象,这也是为什么在创建子类时只需要实现我们需要的附加方法,必须重新实现的也只是那些需要替代的方法。
property() 修饰器函数是一个内置函数,至多接受4个参数:一个获取者函数,一个设置者函数,一个删除者函数以及一个 docstring 。
为将属性转换为可读/可写的特性,我们必须创建一个私有属性,其中实际上存放了数据并提供获取者方法与设置者方法。接下来我们对 Circle 类的 radius 属性进行验证。
class Circle(Point):
def __init__(self, radius, x=0, y=0):
super().__init__(x, y)
self.radius = radius
@property
def radius(self):
""" The circle's radius
>>>circle = Circle(-2)
Traceback(most recent call last):
...
AssertionError:radius must be nonzero and non-negative
>>>circle = Circle(4)
>>>circle.radius = -1
Traceback(most recent call last):
...
AssertionError:radius must be nonzero and non-negative
>>>circle.radius = 6
"""
return self.__radius
@radius.setter
def radius(self, radius):
assert radius > 0, "radius must be nonzero and non-negative"
self.__radius = radius
...
测试:
import Shape
circle = Shape.Circle(-4)
执行之后会报错:
AssertionError: radius must be nonzero and non-negative
这样就会在创建 Circle 对象时进行验证了。
本文介绍了 Python 对面向对象程序设计的基础知识。展示了纯过程型程序设计的一些不足,并使用面向对象来克服这些不足。