Python程序设计——对象和类

学习目标

  • 描述对象和类,以及使用类来建模对象
  • 定义带数据域和方法的类
  • 使用构造方法调用初始化程序来创建和初始化数据域以构建一个对象
  • 使用圆点运算符(.)访问对象成员
  • 使用self参数引用对象本身
  • 使用UML图符号来描述类和对象
  • 区分不可变对象和可变对象
  • 隐藏数据域以避免数据域损坏并使类更易于维护
  • 在软件开发过程中应用类的抽象和封装
  • 探究面向过程范式和面向对象范式的差异

一、为对象定义类

关键点:类定义对象的特征和行为。

        之前的文章介绍了对象和方法,并展示如何使用对象。对象由类创建。本节将详细介绍如何定义自定制的类。

        面向对象程序设计(OOP)是关于如何使用对象创建程序。对象代表现实世界中可以被明确辨识的实体。例如:一个学生、一张桌子、一个圆、一个按钮甚至一笔贷款都可以认为是一个对象。一个对象有独特的特性、状态和行为。

  • 一个对象的特性就像人的身份证号码。Python会在运行时自动对每个对象赋予一个独特的id来辨识这个对象。
  • 一个对象的状态(也被称为它的特征或属性)是用变量表示的,称之为数据域。例如:一个圆对象具有数据域radius,它表示圆的一个属性。一个矩形对象有数据域width和height,它们表示矩形的属性。
  • Python使用方法来定义一个对象的行为(也称为它的动作)。回顾一下,方法也被称为函数。通过调用对象上的方法,你可以让对象完成某个动作。例如:你可以为圆对象定义名为getArea()和getPerimeter()的方法。这样,圆对象就可以调用getArea()方法返回它的面积,调用getPerimeter()方法来返回它的周长。

        使用通用类来定义同一种类型的对象。类和对象的关系就像苹果派食谱和苹果派之间的关系。你可以根据一张苹果派食谱(类)制作出任意多个苹果派(对象)。

        一个Python类使用变量存储数据域,定义方法来完成动作。类就是一份契约(有时也称之为模板或蓝本),它定义对象的数据域和方法。

        对象是类的一个实例,你可以创建一个类的多个对象。创建类的一个实例的过程被称为实例化。术语对象和实例经常是可互换的。对象就是实例,而实例就是对象。

1.1、定义类

        除了使用变量存储数据域和定义方法,一个类还提供了一种特殊的方法:_ _init_ _ 。 这个方法被称为初始化程序,它是在创建和初始化这个新对象时被调用的。初始化程序能完成任何动作,但初始化程序被设计为完成初始化动作,例如:使用初始值创建对象的数据域。

Python使用下面的语法定义一个类:

class ClassName :
    initializer
    methods

        下面程序清单定义了Circle类。类名通常是在关键词class之后,其后紧随一个冒号( : )。初始化程序总是被命名为_ _ init_ _ (第5行),这是一个特殊的方法。注意: init需要前后加两个下划线。数据域radius在初始化程序中创建(第6行)。定义方法getPerimeter和getArea返回一个圆的周长和面积(第8~ 12行)。接下来的几节会介绍更多关于初始化程序、数据域和方法的细节。

import math


class Circle:
    # Construct a circle object
    def __init__ (se1f, radius = 1):
        self.radius = radius

    def getPerimeter(self) :
        return 2 * self.radius * math.pi

    def getArea(se1f) :
        return self.radius * self.radius * math.pi

    def setRadius(se1f, radius):
        self.radius = radius

        注意:类名的命名风格在Python库中不是始终如一的。在本书中,我们会采用类名中每个单词的首字母大写的方式。例如: Circle、 Linear Equation和LinkedList都是遵循我们习惯的正确类名。

1.2、构造对象

        一旦定义了一个类,你就可以使用构造方法由类来创建对象。构造方法完成两个任务:

  • 在内存中为类创建一个对象。
  • 调用类的_ _init_ _方法来初始化对象。

        包括初始化程序的所有方法,都有第一个参数self。这个参数指向调用方法的对象。__init__方法中的self参数被自动地设置为引用刚被创建的对象。你可以为这个参数指定任何一个名字,但是按照惯例,经常使用的是self。

        构造方法的语法规则是:

        类名(参数)

        图7-3显示对象是如何被创建并初始化的。在对象被建立之后,self可以被用来指向对象。

        构造方法的参数和无self的__ init__ 方法中的参数匹配。例如:因为程序清单中的第5行__ init__ 方法被定义为__ init__ (self,radius=1), 所以,为了构建一个半径radius为5的Circle对象,那你就应该使用Circle(5)。图7-4显示使用Circle(5)构建Circle对象的效果。首先,Circle对象在内存中被创建,然后调用初始化程序将半径radius设置为5。

 Circle类中的初始化程序有默认的radius值1。接下来,构造方法创建了默认半径为1的Circle对象:

Circle()

1.3、访问对象成员

        对象成员是指它的数据域和方法。数据域也被称为实例变量,因为每个对象(实例)的数据域中都有一个特定值。方法也被称为实例方法,因为方法被一个对象(实例)调用来完成对象上的动作,例如,改变对象数据域中的值。为了访问一个对象的数据域以及调用对象的方法,你需要使用下面的语法将对象赋给一个变量:

objectRefVar = ClassName(arguments)

例如:

c1 = Circle(5)
c2 = Circle()

        你可以使用圆点运算符(.)访问对象的数据域并调用它的方法,它也被称为对象成员访问运算符。使用圆点运算符的语法是:

objectRefVar.datafield
objectRefVar.method(args)

        注意:通常,你创建一个对象并将它赋给一个变量。随后,你可以使用变量指代这个对象。偶尔,对象也不需要随后被引用。在这种情况下,你可以创建一个对象而不需要明确将它赋值给变量,如下所示:

print("Area is",Circ1e(5).getArea())

        这个语句创建Circle对象并调用它的getArea方法来返回它的面积。以这种方式创建的对象被称为匿名对象。

1.4、self参数

        如之前提到的,定义的每个方法的第一个参数就是self。这个参数被用在方法的实现中,但不是用在方法被调用的时候。那么,这个参数self是干什么的?为什么Python需要它?self是指向对象本身的参数。你可以使用self访问在类定义中的对象成员。例如:你可以使用语法self.x访问实例变量x,而使用语法self.ml()来调用类的对象self的实例方法ml,如图7-5所示。

        一旦一个实例变量被创建,那么它的作用域就是整个类。在图7-5中,self.x是一个在__init__方法中创建的实例变量。它可以在方法m2中被访问。实例变量self.y 在方法ml中被设置为2,在方法m2中被设置为3。注意:你也可以在方法中创建局部变量。局部变量的作用域是在该方法内。局部变量z在方法m1中被创建,而它的作用域就是从它创建时起到方法m1结束。


 

1.5、举例:使用类

        前面几节演示了类和对象的概念。我们已经学习了如何使用初始化程序、数据域和方法定义一个类,以及如何使用构造方法创建一个对象。本节给出一个测试程序构建半径分别为1、25、125的三个圆对象,下面程序清单中显示每个圆的半径和面积。然后,程序将第二个对象的半径改为100,并显示它的新半径和面积。

        程序使用Circle类创建Circle 对象。这种使用类(例如: Circle) 的程序被称作类的客户端。Circle类被定义在程序清单7-1中,这个程序在第1行使用语法from Circle import Circle将其导入。程序创建了一个默认半径为1的Circle对象(第5行)并创建两个指定半径的Circle对象(第10、15行),然后获取radius属性,并调用对象上的getArea()方法获取面积(第7、12、17行)。程序给Circle2设置一个新的radius属性(第20行)。这个也可以通过使用circle2.setRadius ( 100)完成。

        注意:看似保存一个对象的变量实际上包含的是指向这个对象的引用。严格地讲,变量和对象是不同的,但大多数情况下,两者的区别是可以忽略的。所以,为了简单起见,最好说“circlel 是一个Circle对象”,而不会长冗地描述为“ circlel是一个变量,它包含一个指向Circle对象的引用”。

二、不变对象和可变对象

关键点:当将一个可变对象传给函数时,函数可能会改变这个对象的内容。

        回顾一下,Python中的数字和字符串都是不可变对象。它们的内容不能被改变。当将一个不可变对象传给函数时,对象不会被改变。但是,如果你给函数传递一个可变对象,那么对象的内容就可能有变化。程序清单中的例子演示不可变对象和可变对象参数在函数中的不同。

from Circle import Circle
    def main():
        # Create a Circle object with radius 1
        myCircle = Circle()

        # Print areas for radius 1,2,3,4,and 5
        n=5
        printAreas (myCircle,n)

        # Display myCircle. radius and times
        print("\nRadius is", myCircle. radius)
        print("n is",n)


# Print a table of areas for radius
def printAreas(c, times) :
    print("Radius \t\tArea")
    while times >= 1:
    print(c.radius,"\t\t", c.getArea())
    c.radius = c.radius + 1
    times = times - 1

main() # Call the main function

        在上面程序清单中定义了Circle类。程序传递一个Circle对象myCircle和一个int对象n去调用printAreas(myCircle,n) (第9行),它打印一个半径分别为1、2、3、4和5所对应的面积的列表,如样本输出所示。当你将一个对象传递给函数,就是将这个对象的引用传递给函数。但是,传递不可变对象和可变对象之间还有更重要的区别。

  • 像数字或字符串这样的不可变对象参数,函数外的对象的原始值并没有被改变。
  • 像圆这样的可变对象参数,如果对象的内容在函数内被改变,则对象的原始值被改变。

        在第20行,Circle 对象c的radius属性增加1。c.radius+1 创建了一个新的int对象,并将它赋值给c.radius。myCircle 和c都指向同一个对象。当printAreas函数完成后,c.radius是6。所以,由第12行可看出,myCircle.radius的输出结果是6。在第21行,times-1创建一个新的int对象,它被赋值给times。在函数printAreas之外,n还是5。所以,在第13行,n的输出还是5。

三、隐藏数据域

关键点:使数据域私有来保护数据,让类更易于维护。

你可以通过对象的实例变量直接访问数据域。例如:下面的代码,让你通过c.radius访
问圆的半径,它是合法的:

>>> c = Circle(5)
>>> c.radius = 5.4 # Access instance variable di rectly
>> print(c.radius) # Access instance variable di rectly
5.4
>>>

但是,直接访问对象的数据域不是一个好方法,原因有两个。

  • 首先是因为数据可能会被篡改。例如: TV类中的channel取值在1和120之间,但是,它也可能被错误地设置为一个不合法的值( 例如: tv1.channel=125 )。
  • 其次是因为类会变得难以维护并且易于出错。假设你想修改Circle类以保证确保在其他程序用过这个类后半径是非负值。你就不仅仅需要更改Circle类,还得更改使用它的程序,因为客户端可能直接修改半径(例如: myCircle.radius=-5 )。

        为避免直接修改数据域,就不要让客户端直接访问数据域。这被称为数据隐藏,并可以通过定义私有数据域实现。在Python语言中,私有数据域是以两个下划线开始来定义的。你也可以以两个下划线开始来定义私有方法

        私有数据域和方法可以在类内部被访问,但它们不能在类外被访问。为了让客户端访问数据域,就要提供一个get方法返回它的值。为了使数据域可以被更改,就要提供一个set方法去设置一个新值。

        通俗地讲,get 方法是指获取器(或访问器),set 方法是指设置器(或修改器)。

一个get方法有下面的方法头:

def getPropertyName(self):

如果返回类型是布尔型,那么习惯上get方法被如下定义:

def isPropertyName(self):

一个set方法有下面的方法头:

def setPropertyName(self, propertyValue):

        下面程序清单通过在属性名前加两个下划线(第6行)将radius属性定义为私有的来修改上面程序清单中Circle类。

import math

class Circle:
    # Construct a circle object
    def__ init__ (self, radius = 1):
        se1f.__radius = radius

    def getRadius(self):
        return self.__radius 

    def getPerimeter(self):
        return 2 * self.__radius * math.pi

    def getArea(self):
        return self.__radius * self.__radius * math.pi

        radius属性在新Circle类中不能被直接访问。但是,你可以使用getRadius()方法读取它。例如:

        提示:如果类是被设计来给其他程序使用的,为了防止数据被篡改并使类易于维护,就将数据域定义为私有的。如果这个类只是在程序内部使用,那就没必要隐藏数据域。

        注意:使用两个下划线开头来命名私有数据域和方法,但不要以一个以上的下划线结尾。

        在Python语言中,以两个下划线开头同时以两个下划线结尾的名字具有特殊的含义。

        例如:__ radius 是一个私有数据域,但是__radius__并不是私有数据域。

四、类的抽象与封装

关键点:类的抽象是将类的实现和类的使用分离的概念。类的实现的细节对用户而言是不可见的。这就是类的封装。

        软件开发中有许多不同层次的抽象。类的抽象是指将类的实现和类的使用分离开。类的创建者描述类的功能,让客户端知道如何使用这个类。类是方法以及对这些方法要完成动作的描述的一个集合,它被用来作为给客户端的类合约。

        如图7-8所示,类的用户并不需要知道类是如何实现的。实现的细节被封装并对用户隐藏。这就被称为类的封装。本质上讲,封装将数据和方法整合到一个单一的对象中并对用户隐藏数据域和方法的实现。例如:你可以创建一个 Circle对象,在不知道面积是如何被计算出来的情况下获取圆的面积。因此,类也被称为抽象数据类型(ADT)。

        类的抽象和封装是同一个硬币的两面。许多现实生活的例子阐释了类抽象的概念。例如,考虑建造一个计算机系统。你的个人计算机有许多组件,CPU、存储器、磁盘、主板、风扇等。每个组件都可以被看作是一个具有属性和方法的对象。为了让这些组件一起工作,你只需要知道如何使用每个组件,以及它们之间如何相互作用。你不需要知道每个组件内部是如何工作的。内部实现都是被封装的,而且是对你隐藏的。你甚至可以在不知道每个组件是如何实现的情况下组装一台计算机出来。

        计算机系统精确模拟映射面向对象方法。每个组件可以看作是组件类的一个对象。例如:你可能已经有一个定义电脑中使用的风扇的类,它有像大小、速度等的属性,也有像启动和停止这样的方法。一个特定的风扇就是这个类的一个带有具体属性值的对象。

        从现在以后,对所有的类开发实例,首先使用该类创建一个对象,并尽量使用类的方法,然后再把你的注意力放在它的实现上

五、面向对象的思考

关键点:面向过程范型程序设计的重点在设计函数上。而面向对象范型将数据和方法一起合并到对象中。使用面向对象范型的软件设计的重点是在对象和对象上的操作。

        在面向过程程序设计中,数据和操作是分离的,这种方法论需要将数据发送给方法。面向对象程序设计将数据以及和它们相关的操作一起放在一个对象中。这种方法解决了许多继承自面向过程中的问题。面向对象程序设计方法组织程序的方式在某种程度上反映了现实世界,现实世界中的所有对象都是既和属性相关联又和动作相关联。使用对象可以提高软件复用性,同时可以使程序易于开发和维护。Python 中的程序设计涉及在对象方面的思考;Python程序可被视为相互作用的对象的集合。

六、总结

  • 1.类是一种对象的模板、蓝图、合约和数据类型。它定义了对象的属性,并提供用于初始化对象的初始化程序和操作这些属性的方法。
  • 2.初始化程序总是以__init__命名。每个方法的第-.个参数包括类中的初始化程序,它指向调用这个方法的对象。按照惯例,这个参数以self命名。
  • 3.对象是类的一个实例。你使用构造方法来创建-一个对象,使用圆点运算符(.)通过引用变量来访问对象的成员。
  • 4.实例变量或方法属于类的一个实例。它的使用和每个独立的实例相关联。
  • 5.类中的数据域应该被隐藏以避免被更改并使类易于维护。
  • 6.你可以提供get方法或set方法使客户端可以查看或更改数据。通俗地讲,get 方法被称为获取器(或访问器),而set方法被称为设置器(或修改器)。

猜你喜欢

转载自blog.csdn.net/java_faep/article/details/132300813
今日推荐