面向对象程序设计的思想主要是针对大型软件设计而提出的,它的一个关键性观念是将数据以及对数据的操作封装在一起,组成一个相互依存、不可分割的整体,即不同对象之间通过消息机制来通信或者同步。对于相同类型对象进行分类、抽象后,得出共同的特征而形成了类,面向对象程序设计的关键就是如歌合理的定义这些类并且合理组织多个类之间的关系。以下是一些有关对象的术语的定义:
l 类:用来藐视具有相同属性和方法的对象的集合,类定义了集合中每个对象共有的属性和方法。对象是类的实例;
l 实例变量:定义在方法中的变量只作用于当前实例的类;
l 类变量(属性):类变量在整个实例化的对象中是公用的。类变量定义在类中,且在方法数据之外。类变量通常不作为实例变量使用;
l 数据成员:类变量或实例变量用于处理类以及其实例对象的相关数据;
l 方法重写(方法覆盖):如果从父类继承的方法不能满足子类的需求,就可以对其进行改写;
l 多态(Polymorphism):对不同类的对象使用同样的操作;
l 封装(Encapsulation):对外部世界隐藏对象的工作细节;
l 继承(Inheritance):即一个派生类(derived class)继承基类(base class)的字段和方法。继承允许把一个派生类的对象作为一个基类对象来对待,以普通类为基础建立专门的类对象;
l 实例化(Instance):创建一个类的实例、类的具体对象;
l 方法:类中定义的函数;
l 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
9.1 类的定义与使用
9.1.1 class关键字
Python使用class关键字来定义类,class关键字之后是一个空格,接下来是类的名字,如果派生自其他基类的话则需要把所有基类放到一对圆括号中并使用逗号分隔,然后是一个冒号,最后换行并定义类的内部实现。类名的首字母一般要大写,当然也可以按照自己的习惯定义类名,但是一般推荐参考惯例来命名,并在整个系统的设计和实现中保持风格一致,这一点对于团队合作十分重要。
定义了类之后,就可以用来实例化对象,并通过”对象名.成员”的方式来访问其中的数据成员或成员方法。在Python中,可以使用内置方法isinstance()函数来测试一个对象是否为某个类的实例,此种方法使用方式示例如下:
>>> isinstance(Kee,kee)
True
>>> isinstance(Kee,str)
False
Python还提供了一个关键字pass,执行pass的时候什么也不会发生,可以用在类和函数的定义中或者选择结构中,表示空语句。如果暂时没有确定如何实现某个功能,或者为以后的软件升级预留空间,可以使用此关键字来进行”占位”。
9.1.2 数据成员与成员方法
(1) 数据成员
数据成员用来说明对象特有的一些属性,如人的身份证号、姓名、年龄等等。
数据成员大致可以分为两类:属于对象的数据成员和属于类的数据成员。属于对象的数据成员主要是指在构造函数__init__()中定义的(当然也可以在其他成员方法中定义),定义和使用时必须以self作为前缀,同一个类的不同对象(实例)之间的数据成员之间互不影响;属于类的数据成员是该类所有对象共享的,不属于任何一个对象,在定义类时这类数据成员不在任何一个成员方法中定义中。在主程序中或类的外部,对象数据成员属于实例(对象),只能通过对象名访问;而类数据成员属于类,可以通过类名或对象名访问。另外在Python中可以动态的为类和对象增加成员,这也是Python动态类型的一种重要体现。同时利用类数据成员的共享性,可以实时获得该类的对象数量,并且可以控制该类可以创建的对象最大数量。使用数据成员的方式示例如下:
class Car(object):
price = 100000 #属于类的数据成员
def __init__(self,c)
self.color = c #属于对象的数据成员
car1 = Car(“Red”) #实例化对象
car2 = Car(“Blue”)
print(car1.color,Car.price) #访问对象和类的数据成员
Car.price = 110000 #修改类的属性
Car.name = ‘QQ’ #动态增加类的属性
car1.color = “Yellow” #修改实例的属性
print(car2.color,Car.price,Car.name)
print(car1.color,Car.price,Car.name)
def setSpeed(self,s):
self.speed = s
import tupes
car1.setSpeed = tupes.MethodType(setSpeed,car1) #动态为对象增加成员方法
car1.setSpeed(50) #调用对象的成员方法
print(car1.speed)
(2) 成员方法
方法用来描述对象所具有的行为,例如,列表对象的追加元素、插入元素、删除元素、排序,字符串对象的分隔、排版、连接、替换等。
在类中定义的方法可以粗略分为四大类:公有方法、私有方法、静态方法和类方法。公有方法、私有方法一般是指属于对象的实例方法,其中私有方法的名字以两个下划线(__)开始。每个对象都有自己的公有方法和私有方法,在这两类方法中都可以访问属于类和对象的成员;只能在实例中通过self调用或在外部通过Python支持的特殊方式来调用。
类的所有实例方法都必须至少有一个名为self的参数,并且必须是方法的第一个形参(如果有多个形参的话),self参数代表对象自身。在类的实例方法中访问实例属性时需要以self为前缀。但在外部通过对象名调用对象方法时并不需要传递这个参数,如果在外部通过类名调用属于对象的公有方法,需要显式地为该方法的self参数传递一个对象名,用来明确指定访问哪个对象的数据成员。
静态方法和类方法都可以通过类名和对象名调用,但是不能直接访问属于对象的成员,只能访问属于类的成员。一般将cls作为类方法的第一个参数,表示该类自身,在调用类方法时不需要为该参数传递值。
在Python中,在类中定义实例方法时将第一个参数定义为self只是一个习惯,并不必须使用self这个名字,但是一般也不建议使用别的名字。同样属于类的方法中使用cls作为第一个参数也是一种习惯,也可以使用其他名字作为第一个参数,虽然不建议这样使用。
不同对象实例的数据成员之间互不影响,是不共享的,但是同一个类的素哟有实例方法是在不同对象之间共享的,所有对象都执行相同的代码,通过self参数来判断要处理的是哪个对象的数据。
在Python中,函数和方法是有区别的。方法一般是指与特定实例绑定的函数,通过对象调用方法时,对象本身将被最为第一个参数传递过去,普通函数并不具备这个特点。
用一段代码来展示下它的使用方式,代码示例如下:
>>> class Root:
__total = 0
def__init__(self,v): #构造函数
self.__value = v
Root.__total += 1
def show(self): #普通实例方法
print(‘self.__value:’,self.__value)
print(‘Root.__total:’,Root.__total)
@classmethod #修饰器,声明类方法
def classShowTotal(cls): #类方法
print(cls.__total)
@staticmethod #修饰器,声明静态方法
defstaticShowTotal(): #静态方法
print(Root.__total)
>>> class Demo:
pass
>>> t = Demo()
>>> def test(self,v)
self.value = v
>>> t.test = test #动态增加普通函数
>>> t.test
<function test at 0x000000000034B7EA0>
>>> t.test(t,3)
>>> print(t.value)
3
>>> import types
>>> t.test = types.MethodType(test,t) #动态增加绑定的方法
>>> t.test
<bound method test of <__main__.Demo object at 0x000000000074F9E8>
>>> t.test(5)
>>> print(t.value)
5
(3) 属性
公开的数据成员可以在外部随意访问和修改,这就导致编程者很难控制用户修改新数据的合法性。解决这一问题的常用方法是定义私有数据成员,然后设计公开的成员方法来提供对私有数据成员的读取和修改操作,修改私有数据成员时可以对值进行合法性检测,这提高了程序的健壮性,保证了数据的完整性。此种方法结合了公开数据成员和成员方法的优点,既可以像成员方法那样对值进行必要的检查,又可以像数据成员那样灵活的进行访问。
Python2.x中属性的实现有很多不尽如人意的地方,在Python3.x中,属性得到了较为完整的实现,支持更加全面的保护机制。如果设置属性为只读,则无法修改其值,也无法为对象增加与属性同名的新成员,同时,也无法删除对象属性;如果将属性设置成为了可读、可修改而不允许进行删除,则可以修改其值,但无法删除对象属性;如果将属性设置为可读、可写、可删除,则既可以修改其值又可以为对象增加与属性同名的新城员,还可以删除对象属性。
(4) 特殊方法与运算符重载
在Python中有大量的特殊方法被用来支持更多的功能,例如,运算符重载就是通过在类中重写特殊函数来实现的。在自定义类时如果重写了某个特殊方法即可支持对应的运算符,具体实现什么工作则完全可以根据需要来定义。表9.1列出了部分较常见的特殊类方法,完整列表请参考:(https://docs.python.org/3/reference/datamodel.html#special-method-name)。
表9.1 Python类特殊方法
方法 |
功能 |
__new__() |
类的静态方法,用于确定是否要创建对象 |
__init__() |
构造函数,生成对象时调用 |
__del__() |
析构函数,释放对象时调用 |
__add__() |
+ |
__sub__() |
- |
__mul__() |
* |
__truediv__() |
/ |
__floordiv__() |
// |
__mod__() |
% |
__pow__() |
** |
__repr__() |
打印,转换 |
__setitem__() |
按照索引赋值 |
__getitem__() |
按照索引获取值 |
__len__() |
计算长度 |
__call__() |
函数调用 |
__contains__() |
in |
__eq__()、__ne__()、__lt__()、__le__()、 __gt__()、__ge__() |
==、!=、<、<=、>、>= |
__str__() |
转化为字符 |
__lshift__()、__rshift__() |
<<、>> |
__and__()、__er__()、__invert__()、__xor__() |
&、|、~、^ |
__iadd__()、__isub__() |
+=、-= |
9.1.3 创建对象
在Python内部包含了几种内建对象类型(数字,字符串,列表,元组和字典)。同时Python也可以创建自己的对象和函数。先来大概了解下多态,封装和继承。
l 多态:是对不同的对象使用同样的操作,也就是说给每个对象定义同样的函数。例如对于元组和字典可以定义同样的函数count,以计算元组和字典的长度;
l 封装:对外部世界隐藏对象的工作细节;
l 继承:由子类继承父类的属性。
以下面一段代码为例来演示如何创建对象,示例代码如下:
class Person:
#define class
<span style="white-space:pre"> </span>age=450
<span style="white-space:pre"> </span>def setName(self,name):
#public method
<span style="white-space:pre"> </span>self.name=name
<span style="white-space:pre"> </span>def getName(self):
<span style="white-space:pre"> </span>return self.name
<span style="white-space:pre"> </span>def __inaccessible(self):
#private method
<span style="white-space:pre"> </span>print "you can acces this method"
<span style="white-space:pre"> </span>
foo=Person()
foo.setName("gaoxiang")
print(foo.getName())
print(foo.name)
#print(foo.__age)
#you can`t access the attribute age because the__age is private
#foo.__inaccessible
在上述代码中声明了一个叫做Person的类,其中包括了两个方法,setName()和getName()。对于setName方法需要两个参数self和name,其中self是指自身,用于对自身属性的访问,而name则是有对象调用函数时传入的。对于getNname方法只有一个参数self,用于对自身属性的访问。第三个方法__inaccessable为private method 外部的对象不能访问该方法。
foo=Person() 该语句创建一个Person类型的对象,该对象可以调用类中事先定义好的函数
需要说明的一点是对象自身可以直接对其属性进行访问,eg:foo.name(name为对象的一个属性)。但是foo却不能直接访问__age因为__age是一个私有变量。
class student(Person):
def setName(self,name):
print("I am a student")
self.name=name
foo2=student()
foo2.setName("baiyanlang")
print(foo2.getName())
子类继承父类,子类中可以覆盖父类的方法,同时子类也可以定义自己的方法。
有了面向对象的设计,Python程序的书写将会更加简单。
9.2 构造方法与析构方法
在Python中没有专门用于构造和析构的函数,但是一般可以在__init__()和__del__()函数中分别完成初始化和删除操作,可以用这两个方法来电梯构造方法和析构方法。
(1) 构造方法__init__()
在Python中,构造方法__init__()的意义重大,这是因为它不仅是在对象生命周期中初始化最重要的一步,每个对象都必须正确初始化之后才能正常工作,还因为它的参数值可以有多种形式。在讲解它之前,先简单的了解下在Python中隐含的object类的层次结构。
每一个Python类都隐含了一个超类:object。它是一个非常简单的定义几乎不做任何事情。可以创建object的实例,但是不能用它做太多,因为许多特殊的方法容易抛出异常。当余姚自定义一个类时,object为超类。在某些情况下,超类特殊方法的行为是编程者所需要的,而在有些情况下,编程人员需要覆盖这个特殊方法。下面是一个类定义实例,它使用新的名称简单的继承了object:
class x:
pass
① 基类对象的__init__()方法。
所有的超类object中,都有一个默认包含pass的__init__()实现,这个方法不需要被编程人员去实现,如果不实现它,则在对象创建后就不会创建实例变量,在某些情况下,这种默认行为是可以接受的。用一个例子展示下,示例代码如下:
class Rectangle:
def area(self):
return self.length * self.width
r = Rectangle()
r.length, r.width = 13, 8
r.area()
运行结果为104。
这个设计给予编程者很大的灵活性。一个可选属性就是一个子类,只是没有对它进行正式的声明。一个好的__init__()f方法应该让实例变量成为显式地变量。
② 在超类中实现__init__()方法
通过实现__init__()方法来初始化对象。当一个对象被创建,Python首先创建一个空对象,然后为那个新对象调用__init__()方法。这个方法函数通常用来创建对象的实例变量并执行任何其他一次性处理。
下面是Card类示例定义的层次结构。在此层次结构中将定义Card超类和三个子类,这三个子类是Card的变种。两个实例变量直接由参数值设置,两个变量通过初始化方法计算。示例代码如下:
class Card:
def __init__(self, rank, suit):
self.suit = suit
self.rank = rank
self.hard, self.soft = self._points()
class NumberCard(Card):
def _points(self):
return int(self.rank), int(self.rank)
class AceCard(Card):
def _points(self):
return 1, 11
class FaceCard(Card):
def _points(self):
return 10, 10
在这个示例中,提取__init__()方法到超类,这样在Card超类中的通用初始化可以适用于三个子类NumberCard、AceCard和FaceCard。
这是一种常见的多态设计。每一个子类都提供一个唯一的_points()方法实现。所有子类都有相同的签名:有相同的方法和属性。这三个子类的对象在一个应用程序中可以交替使用。
③ 使用__init__()创建显式常量
在某些情况下,会有一个在初始化或配置文件中创建的常量对象池,或者编程者可以基于命令行参数创建常量对象。在Python中,并没有一个简单正式的机制来定义一个不可变对象,在比较复杂的情况下,会有一些策略或状态对象通过__init__()方法进行显式地创建,通过小的,静态的常量对象的复用就可以使策略或状态设计模式变的更有效率。必须承认,在Python中这些对象并不是技术上一成不变的,而是可变的。进行额外的编码能使得这些对象成为真正不变的,可能会在有些时候为编程人员带来一些好处。
使用__init__()方法创建显式常量的方法如下:
class Suit: #创建类
def __init__(self, name, symbol):
self.name= name
self.symbol= symbol
#通过Suit类来创建常量
Club,Diamond,Heart,Spade=Suit('Club','?'),Suit('Diamond','?'),Suit('Heart','?'), Suit('Spade','?')
#创建cards
cards = [AceCard('A', Spade), NumberCard('2', Spade), NumberCard('3', Spade),]
(2) 析构方法__del__()
先来看一段示例代码:
class Test(object):
def __init__(self, name):
self.name = name
print('这是构造函数')
def say_hi(self):
print('hell, %s' % self.name)
def __del__(self):
print('这是析构函数')
obj = Test('bigberg')
obj.say_hi()
del obj
此段示例代码执行结果如下:
这是构造函数
hello bigberg
这是析构函数
“__del__”就是一个析构函数了,当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间。__del__()也是可选的,如果不提供,则Python 会在后台提供默认析构函数。如果要显式的调用析构函数,可以使用del关键字:del obj。当用del删除一个对象时,其实并没有直接清除该对象的内存空间。Python采用’引用计数’的算法方式来处理回收,即当某个对象在其作用域内不再被其他对象引用的时候,Python就自动清除对象。而析构函数__del__()在引用的时候就会自动清除被删除对象的内存空间。
9.3 成员访问权限
在Python中默认的成员函数,成员变量都是公开的,没有类似public,private等关键字来修饰成员函数和成员变量,从形式上看,在定义类的成员时,如果成员名以两个下划线(__)开头则表示是私有成员。私有成员在类的外部不能直接进行访问,但是在Python中,并没有对私有成员提供严格的访问保护机制,在内部,Python使用一种name mangling技术,将_membername替换成_classname_membername,所以在外部使用原来的私有成员的名字时会提示找不到。一般是在类的内部进行访问和操作,或者在类的外部通过调用对象的公有成员方法来进行访问。另外Python提供了一种特殊的方式”对象名._类名__xxx”可以访问私有成员,但这会破坏类的封装,不推荐这样做。公有属性是可以公开使用的,既可以在类的内部进行访问,也可以在类的外部程序中进行使用。
在Python中,以下划线开头和结束的成员名有特殊的含义,类定义中用下划线作为变量名和方法名前缀和后缀来表示类的特殊成员,以以下三种为例:
l _xxx:保护成员,不能用’from module import *’导入,只有类对象和子对象可以访问这些成员;
l __xxx__:系统定义的特殊成员;
l __xxx:类中的私有成员,一般只有类对象自己能访问,子类对象也不能访问到这个成员,但在对象外部可以通过”对象名._类名__xxx”这样的特殊方式来访问。
9.4 继承
在面向对象编程中,继承是代码复用和设计复用的重要途径,是面向对象程序设计的重要特性之一,继承也是实现多态的必要条件之一。
设计一个新类时,如果可以继承一个已有的设计良好的类然后进行二次开发,无疑会大幅度减少开发工作量,并且可以很大程度地保证质量。在继承关系中,已有的、设计好的类称为父类或基类,新设计的类称为子类或派生类。派生类可以继承父类的公有成员,但是不能继承其私有成员。如果需要在派生类中调用基类的方法,可以使用内置函数super()或者通过”基类名.方法名()”的方式来实现这一目的。
Python支持多类继承,如果父类中有相同的命名方法,而在子类中使用时没有指定父类名,则Python解释器将从左向右按顺序进行搜索。所谓多态,是指基类的同一个方法在不同派生类对象中具有不同的表现形式和行为。派生类继承了基类的行为和属性之后,还会增加某些特定的行为和属性,同时还可能会对继承来的某些行为进行一定的改变,这恰恰是多态的表现形式。在Python中主要通过重写基类的方法来实现多态。下面的代码完整的描述了类的继承机制,请认真体会构造函数、私有方法以及普通公开方法的继承原理。
>>> class A():
def __init__(self):
self.__private()
self.__public()
def __private(self):
print('__private()method of A')
def public(self):
print('public()method of A')
def __test(self):
print('__test()method of A')
>>> class B(A):
def __private(self): #此处B类没有构造函数
print(‘__private()method of B’)
def public(self):
print(‘public()method of B’)
>>> b = B() #创建派生类对象
__private()method of A
public()method of A
>>> b.__test() #派生类没有继承基类中的私有成员方法
Traceback(most recent call last):
File”<pyshell#215>”,line 1,in <module>
b.__test()
AttributeError:’B’object has no attribute ‘__test’
>>> class C(A) #注意,C类有构造函数
def __init__(self):
self.__private()
self.public()
def __private(self):
print(‘__private()method of C’)
def public(self):
print(‘public()method of C’)
>>> c = C()
__private()method of C
public()method of C
本人自己用python中的pygame库写了一个贪吃蛇小游戏,有兴趣的童鞋可以去文件中一探究竟哦~