面向对象编程
在软件开发中,对象是一个数据及相关行为的集合。而面向对象就是功能上通过数据和行为的方式来描述对象协作交互的集合。在一般的面向对象范畴里,在整个软件开发的过程中,面向对象分析(OOA)、面向对象设计(OOD)和面向对象编程(OOP)是软件工程的三个阶段。OOA着眼于若干用例的需求分析,识别对象及对象之间的协作交互;OOD需要将需求转化为一套类和接口,对象的状态数据通过类的的属性来体现,对象的行为通过类的方法或接口的方法来定义,通过建模语言(UML)来展现,这些类和接口可以通过任何面向对象的编程语言来实现;OOP是把建模语言(UML)定义的模型设计转化为一个可以执行的程序的过程,具体需要选择某一种支持面向对象的编程语言,在这里针对Python编程语言。
类定义格式如下:
class <类名>:
类体
类的定义以关键字class开头,之后跟一个名字(标识符)用来标识这个类,并以冒号结尾。类中类体除了定义函数,还可以包含其他语句,一个类中的函数定义通常有一个特殊的参数列表形式,以在类外调用这个函数。
>>> class Point:
... pass
...
>>>
类的命名必须符合标准的Python的变量命名规则:必须以字母或下划线开头,名字中只能包含字母、下划线或数字。
类的名字建议使用驼峰式标记:以大写字母开头的单词或标识符,要求紧随其后的任何一个单词或另一标识符的首字母都要大写。
紧跟类的定义的下一行,是类体的内容,与Python的其它构建一样,使用缩进来界定该类的界限,以四个空格代表缩进。由于在这里设计的Point类没有实际功能,在缩进这一行使用了pass这个关键字是为了保持程序结构的完整性,并不意味着具体功能,是空语句,一般用作占位语句。
对象是一个具有相应行为的数据的集合,而类是描述了对象,是创建对象的模板。对象是类的实例,每一个对象都是不同的个体,但确有着关联同一个类的属性和方法。
>>> class Point:
... pass
...
>>> p1 = Point()
>>> p2 = Point()
>>> p1.x,p1.y = 5,6
>>> p2.x,p2.y = 8,3
>>> print(p1.x,p1.y)
5 6
>>> print(p2.x,p2.y)
8 3
>>>
以上语句代码首先定义了一个没有任何属性和方法的空的类Point,然后实例化了Point类的两个对象p1和p2,并且给每个对象赋予了x属性和y属性来标识一个二维空间的坐标点。这个是通过<object>.<attribute> = <value>
来为属性赋值,此方式称为点记法。这个值<value>
可以是任意的:可以是一个Python原始的内置数据类型,也可以是其它的对象,甚至可以是一个函数或一个其它类。
类支持两种操作:属性引用和实例化。
属性引用: 调用类的属性:class.name,name是类中定义的属性变量或方法(函数)的名字。
>>> class Point:
... i = 2
... def reset(self):
... self.x = self.i
... self.y = self.i
...
>>> Point.i = 0
>>> p = Point()
>>> Point.reset(p)
>>> print(p.x,p.y)
0 0
>>>
Point.i
和Point.reset
是有效的属性引用,分别引用一个整数和一个方法。Python的类中的方法和定义一个函数相同,以关键字def开头,紧跟一个空格和方法名,方法名后紧跟一对小括号,括号内包含参数列表,然后以冒号结尾。下一行的缩进语句是这个方法内部的任意的Python代码,可以操作对象本身和任意传入的参数。
类中的方法和普通函数有一点不同,所定义的方法的参数列表中必须包含一个参数,这个参数就是self
,它是对调用这个方法的对象的引用。
Python没有静态类型的修饰,“成员变量”必须要加self.
前缀,否则就变成类的属性(相当于 C++ 静态成员),而不是对象的属性了。
类实例化:创建类的新实例并将对象分配给本地变量。
>>> class Point:
... def __init__(self, x=0, y=0):
... self.move(x, y)
... def move(self, x, y):
... self.x = x
... self.y = y
... def reset(self):
... self.move(0, 0)
...
>>> p = Point(3, 6)
>>> p.counter = 1
>>> while p.counter < 10:
... p.move(p.x+p.counter,p.y+p.counter)
... print(p.x,p.y)
... p.counter = p.counter * 2
...
4 7
6 9
10 13
18 21
>>> del p.counter
>>>
当一个类定义了一个init()方法时,类会自动调用init()方法初始化新创建的类实例。可以通过p = Point()
的方式获得,在这种情况下,赋予类实例化的参数被传递给init()方法。不过此方法有一个特殊的名字__init__,即开始和结尾都是双下划线,Python解释器会特殊对待它。
访问控制:Python 没有 public / protected / private 这样的访问控制,如果要表示“私有”,习惯使用加双下划线前缀。
>>> class Point:
... def __init__(self, x=0, y=0):
... self.move(x, y)
... def move(self, x, y):
... self.__x = x
... self.__y = y
... def __fun(self):
... pass
...
>>>
继承允许在两个类或者更多的类之间,将共同的部分抽象到一个超类里, 特有的细节保留在子类里。在Python中,每一个新建的类都使用了继承,所有的类都是object这个特别类的子类。在创建新类时,若不指明类的继承,则默认从object类继承:
>>> class MyAbc(object):
... pass
...
>>> abc = MyAbc()
>>> abc.x = 23
>>> abc.x
23
>>>
在定义新类时,如果不具体提供一个超类,则该类就会自动从object类继承。超类即父类,是一个被继承的类,而子类就是从父类扩展派生过来的类。在这里超类是object,子类是MyAbc。
在Python中,object类含有两个方法,分别为__new__()
和__init__()
,类的对象的创建和初始化由它们来完成。
__new__()
负责进行对象的创建,object中的__new__()
示例代码如下:
@staticmethod # known case of __new__
def __new__(cls, *args, **kw): # known special case of object.__new__
""" Create and return a new object. See help(type) for accurate signature. """
pass
__new__()
方法天生就是一个静态方法,即使没有@staticmethod修饰符,它也是静态的。它没有使用self参数变量,因为它的工作是创建最终会被赋值给self变量的对象。这个方法创建了一个简单的空对象,除了cls以外,其他的参数和关键字最终都会传递给__init__()
方法,这是Python定义的标准行为。一般情况下不需要重写这个方法,子类的对象的创建将调用基类object.__new__(cls)
方法创建对象。该方法执行的优先级最高。
__init__()
负责进行对象的初始化,object中的__init__()
示例代码如下:
def __init__(self): # known special case of object.__init__
""" Initialize self. See help(type(self)) for accurate signature. """
pass
对象的生命周期主要包括了创建、初始化和销毁。object作为所有类的基类,已经为__init__()
方法提供了默认实现。在子类中可以重写这个方法,都应当显式地指定要初始化的变量。
在一个基本的类的定义中,继承的语法很简洁,仅需要在类名后面的圆括号内包含父类的名称即可:
class 派生类名称(基类名称): # 派生类表示为子类,基类代表父类
pass
举例:
>>> import math
>>> class Shape:
... def area(self):
... return 0.0
...
>>> class Circle(Shape):
... def __init__(self, r=0.0):
... self.r = r
... def area(self):
... return math.pi * self.r * self.r
...
>>> class Rectangle(Shape):
... def __init__(self, a, b):
... self.a, self.b = a, b
... def area(self):
... return self.a * self.b
...
>>> circle = Circle(5.0)
>>> circle.area()
78.53981633974483
>>> rectangle = Rectangle(5.0, 6.0)
>>> rectangle.area()
30.0
>>>
Python 是没有重写(override)的概念的,子类重写父类的方法,只是把相同的属性名绑定到了不同的函数对象。
这个有趣的名字描述了一个简单的概念:对于相同的行为接口,调用不同子类的对象将会产生不同的行为,而无须明确知道这个子类实际上是什么。
Python 没有重写(override)的概念,严格来讲,Python 并不支持「多态」。不过可以人为的设置一些规范,更好的应用 Python 面向接口编程,虽然Python 并没有语法去保证。
首先在名字上标志定义的类是抽象的(Abstract),然后是一种非正式的创建策略:在基类中让每个方法都抛出异常 NotImplementedError,用以标识一些方法必须在子类中提供实现。Python 面向接口的编程,更多的要依赖程序员的素养。
举例:
>>> import math
>>> class AbstractShape:
... def __init__(self, color):
... self.color = color
... def area(self):
... raise NotImplementedError
...
>>> class Circle(AbstractShape):
... def __init__(self, color, r=0.0):
... super().__init__(color)
... self.r = r
... def area(self):
... return math.pi * self.r * self.r
...
>>> class Rectangle(AbstractShape):
... def __init__(self, color, a, b):
... super().__init__(color)
... self.a, self.b = a, b
... def area(self):
... return self.a * self.b
...
>>>
C++ 可以通过纯虚函数或以 protected修饰符设置构造函数来避免接口被实例化,Java 具有接口定义完整的语法支持。而且子类或实现类必须实现“接口”中定义的每一个方法,但在Python中没有强制这样做。
>>> try:
... circle = Circle('red',6.0)
... circle.area()
... rectangle = Rectangle('blue', 8.0, 9.0)
... rectangle.area()
... except NotImplementedError as err:
... print(err, '--> error message')
...
113.09733552923255
72.0
>>>
可以使用abc
模块来丰富抽象基类的实现:
>>> import abc
>>> class AbstractShape(metaclass=abc.ABCMeta):
... def __init__(self, color):
... self.color = color
... @abc.abstractmethod # 利用装饰器修饰area()方法
... def area(self):
... pass
...
>>> class Circle(AbstractShape):
... def __init__(self, color, r=0.0):
... super().__init__(color)
... self.r = r
...
>>> circle = Circle('red',6.0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Circle with abstract methods area
>>>
以上实现带来两个好处:首先,它阻止了对抽象基类AbstractShape的实例化,其次,任何没有提供area()
方法实现的子类也是不能被实例化的。如果试图创建一个类的实例,而这个类并没有提供抽象方法的实现,程序则会抛出一个异常。
一个对象是一系列功能的集合,含有方法和属性。object类的默认行为包括设置、获取和删除属性。
1. 属性的基本操作默认情况下,创建任何类内部的属性(attribute)
都将支持以下4种操作:
创建一个简单的类并将其实例化,来对这些操作进行测试:
>>> class Color:
... pass
...
>>> c = Color()
>>> c.name = "red"
>>> c.name
'red'
>>> c.name
'red'
>>> c.value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Color' object has no attribute 'value'
>>> del c.name
>>> c.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Color' object has no attribute 'name'
>>>
以上代码允许创建、获取、赋值和删除属性。如果试图获取一个未赋值的属性或者删除一个不存在的属性,则会抛出异常。
使用__init__()方法初始化:
>>> class Color:
... def __init__(self, value, name):
... self.__name = name
... self.__value = value
... def set_name(self, name):
... self.__name = name
... def get_name(self):
... return self.__name
...
>>> c = Color("#ff0000", "bright red")
>>> c.get_name()
'bright red'
>>> c.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Color' object has no attribute '__name'
>>>
前缀有一个双下划线的变量(__name, __value
)表明它们是类实例所私有的。直接访问则引发异常。
特性(property)
是一个函数,即代表一个简单的属性。可以获取、设置和删除特性值,正如可以获取、设置和删除属性值。特性和属性的重要区别在于:特性是一个函数,而且可以被调用,而不仅仅是用于存储的对象的引用。另一区别在于:不能轻易地为已有对象添加新特性。但默认可以很容易的给对象添加新属性。
使用两种方式来创建特性,一种方式是使用@property修饰符;另一种方式是使用property()函数。以下介绍使用修饰符的方式:
>>> class Color:
... def __init__(self, value, name):
... self.__value = value
... self.__name = name
... @property
... def name(self):
... return self.__name
... @name.setter
... def name(self, aName):
... self.__name = aName
... @name.deleter
... def name(self):
... del self.__name
...
>>> c = Color("#ff0000", "bright red")
>>> c.name
'bright red'
>>> c.name = 'Pale Green'
>>> c.name
'Pale Green'
>>> del c.name
>>> c.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in name
AttributeError: 'Color' object has no attribute '_Color__name'
>>>
删除通过@name.deleter特性调用完成。通过@property的特性调用,返回实体对象的属性(self.__name ),如果检索不到属性,则引发异常。
属性和特性之间,通过特性进行检索、赋值和删除时,可以自动调用一些自定义的动作。
博文最后更新时间: