面向对象编程是最有效的软件编写方法之一。在面向对象编程中,编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性,使用面向对象编程可模拟现实情景。

根据类来创建对象被称为实例化。可指定在实例中存储什么信息,定义可对这些实例执行哪些操作。还可编写一些类来扩展既有类的功能,让相似的类能够高效地共享代码。也可将把自己编写的类存储在模块中,并在自己的程序文件中导入其他程序员编写的类。

1、创建和使用类

# 大青 2019/7/14

# 创建一个名为Restaurant的类
class  Restaurant():
    def __init__(self,name,type):
        self.name = name   #初始化属性restaurant_name和cuisine_type
        self.type = type
    def describe_restaurant(self):
        print("The Reataurant's name is "+self.name)
        print("The cuisine's type is "+self.type)
    def open_restaurant(self):
        print("The restaurant is opening")

方法__init__() :

类中的函数称为方法 ;方法__init__() 是一个特殊的方法,根据Restaurant类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。

方法__init__() 定义成了包含三个形参:self 、name 和type 。在这个方法的定义中,形参self 必不可少,还必须位于其他形参的前面。Python调用这个__init__() 方法来创建Restaurant实例时,将自动传入实参self 。每个与类相关联的方法调用都自动传递实参self ,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。创建restaurant实例时,Python将调用Restaurant类的方法__init__() 。我们将通过实参向Restaurant() 传递餐馆名字和厨房类型;self 会自动传递, 因此我们不需要传递它。每当我们根据Restaurant类创建实例时,都只需给最后两个形参(name 和type )提供值。

在方法定义的两个变量都有前缀self 。以self 为前缀的变量都可供类中的所有方法使用,还可以通过类的任何实例来访问这些变量。self.name = name 获取存储在形参name 中的值,并将其存储到变量name 中,然后该变量被关联到当前创建的实例。self.type= type 的作用与此类似。像这样可通过实例访问的变量称为属性。

Restaurant类还定义了另外两个方法:describe_restaurant() 和open_restaurant() 。由于这些方法不需要额外的信息,如名字或类型,因此它们只有一个形参self 。后面将创建的实例能够访问这些方法。

根据类创建实例:

#创建实例
my_restaurant = Restaurant('McDonald','Fast food')
#打印属性
print("My Restaurant's name is " + my_restaurant .name)
print("My cuisine's type is "+my_restaurant .type)

创建实例restaurant,Python创建一条名字为'McDonald' 、类型为'Fast food' 的餐馆。遇到这行代码时,Python使用实参'McDonald' 和'Fast food'  调用Restaurant类中的方法__init__() 。方法__init__() 创建一个表示特定餐馆的实例,并使用我们提供的值来设置属性name 和type。方法__init__() 并未显式地包含return 语句, 但Python自动返回一个表示这家餐馆的实例。将这个实例存储在变量my_restaurant中。在这里,命名约定:通常可以认为首字母大写的名称(如Restaurant)指的是类,而 小写的名称(如my_restaurant)指的是根据类创建的实例。

访问属性:可用句点表示法。

my_restaurant.name

上面属性打印结果为:

My Reataurant's name is McDonald
My cuisine's type is Fast food

调用方法:根据Restaurant类创建实例后,就可以使用句点表示法来调用Restaurant类中定义的任何方法。

#创建实例
my_restaurant = Restaurant('McDonald','Fast food')

#调用方法
my_restaurant.describe_restaurant()
my_restaurant.open_restaurant()

要调用方法,可指定实例的名称(这里是my_restaurant)和要调用的方法,并用句点分隔它们。遇到代码my_restaurant.describe_restaurant()时,Python在类Restaurant中查找方法describe_restaurant() 并运行其代码。 Python以同样的方式解读代码my_restaurant.open_restaurant() 。

调用结果:

The Reataurant's name is McDonald
The cuisine's type is Fast food
The restaurant is opening

创建多个实例:可按需求根据类创建任意数量的实例。下面再创建一个名为your_ restaurant的实例:

my_restaurant = Restaurant('McDonald','Fast food')
your_restaurant = Restaurant('Haidilao','Hot pot')

print("My Reataurant's name is " + my_restaurant .name)
print("My cuisine's type is "+my_restaurant .type)
my_restaurant .open_restaurant()

print("Your Reataurant's name is " + your_restaurant .name)
print("Your cuisine's type is "+your_restaurant .type)
your_restaurant .open_restaurant()

在这个实例中,我们创建了两家餐馆。每家餐馆都是一个独立的实例,有自己的一组属性,能够执行相同的操作:

My Reataurant's name is McDonald
My cuisine's type is Fast food
The restaurant is opening
Your Reataurant's name is Haidilao
Your cuisine's type is Hot pot
The restaurant is opening

2、使用类和实例

还是上面的的Resturant类。添加了一个number_served的属性,并将其初始值设置为了0。类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法__init__() 内指定这种初始值是可行的;如果你对某个属性这样做 了,就无需包含为它提供初始值的形参。

# 创建一个名为Restaurant的类
class  Restaurant():
    def __init__(self,name,type):
        self.name = name   #初始化属性restaurant_name和cuisine_type
        self.type = type
        self.number_served = 0
    def describe_restaurant(self):
        print("The Reataurant's name is "+self.name)
        print("The cuisine's type is "+self.type)
    def open_restaurant(self):
        print("The restaurant is opening")

#创建实例
restaurant = Restaurant('kk','rice')

直接修改属性的值:

要修改属性的值,最简单的方式是通过实例直接访问它。

restaurant = Restaurant('kk','rice')
restaurant.number_served = 100
print("It has served  "+str(restaurant.number_served))

其中restaurant.number_served = 100代码让Python在实例restaurant中找到属性number_served,并将该属性的值设置为100:

It has served  100

通过方法修改属性的值:

在Restaurant类添加了方法set_number_served() 。这个方法接受一个人数,并将其存储到self.number_served中。

def set_number_served(self,num):
        self.number_served = num
        print("It has served  " + str(self.number_served))

然后在实例中调用set_number_served() ,并向它提供了实参50。它将服务的人数设置为50;

#创建实例
restaurant = Restaurant('kk','rice')
restaurant.set_number_served(50)

打印结果:

It has served  50

3、继承

如果编写的类是另一个现成类的特殊版本,可使用继承 。一个类继承另一个类时,它将自动获得另一个类的所有属性和方法;原有的类称为父类 ,而新类称为子类 。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。(注意:python函数使用return返回值,如果不用return, 而用print输出值,这个函数默认还有一个返回值为None。)

# 创建一个名为Restaurant的类
class  Restaurant():
    def __init__(self,name,type):
        self.name = name   #初始化属性restaurant_name和cuisine_type
        self.type = type
        self.number_served = 0
    def describe_restaurant(self):
        print("The Reataurant's name is "+self.name)
        print("The cuisine's type is "+self.type)
    def open_restaurant(self):
        print("The restaurant is opening")
    def set_number_served(self,num):
        self.number_served = num
        print("It has served  " + str(self.number_served))

class FashinRestaurant(Restaurant):
    def __init__(self,name,type):
        super().__init__(name,type)

fRestaurant = FashinRestaurant('Vienna','Western')
print(fRestaurant.describe_restaurant())

首先是Restaurant类的代码。创建子类时,父类必须包含在当前文件中,且位于子类前面。定义了子类FashionRestaurant 。定义子类时,必须在括号内指定父类的名称。

方法__init__() 接受创建Restaurant实例所需的信息。 super() 是一个特殊函数,帮助Python将父类和子类关联起来。这行代码让Python调用FashionRestaurant 的父类的方法__init__() ,让FashionRestaurant 实例包含父类的所有属性。父类也称为超类 (superclass),名称super因此而得名。

创建一实例,并将其存储在变 量fRestaurant 中。这行代码调用FashionRestaurant 类中定义的方法__init__() ,后者让Python调用父类Restaurant 中定义的方法__init__() 。FashionRestaurant 实例的行为与Restaurant 实例一样。

The Reataurant's name is Vienna
The cuisine's type is Western

给子类定义属性和方法:

一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法。

# 创建一个名为Restaurant的类
class  Restaurant():
   #skip

class FashinRestaurant(Restaurant):
    def __init__(self,name,type):
        super().__init__(name,type)
        self.kinds = 100

    def describe_food_kind(self):
        print("This restaurant has  "+str(self.kinds)+" kinds of food")

fRestaurant = FashinRestaurant('Vienna','Western')
print(fRestaurant.describe_restaurant())
fRestaurant.describe_food_kind()

添加了新属性self.kinds,并设置其初始值100。根据FashionRestaurant类创建的所有实例都将包含这个属性,但所有Restaurant 实例都不包含它。还添加了一个名为describe_food_kind() 的方法,它打印有关食物种类的信息。调用这个方法时,会看到一条时尚餐馆特有的描述:

This restaurant has  100 kinds of food

重写父类:

对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与要重写的父类方法同名。这样,Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。

列如Restaurant类有一个名为open_restaurant() 的方法,想改进下内容可以重写它:

class FashinRestaurant(Restaurant):
    def __init__(self,name,type):
        super().__init__(name,type)
        self.kinds = 100
        self.status = 1
  

    def open_restaurant(self):
        if self.status == 1:
            print("The restaurant is opening")
        else:
            print("The restaurant is closed")

对FashionRestaurant调用方法open_restaurant() ,Python将忽略Restaurant类中的方法open_restaurant() ,转而运行上述代码。

将实例用作属性:

使用代码模拟实物时,可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。 可将大型类拆分成多个协同工作的小类。

列如不断给FashionRestaurant类添加细节时,可能会发现其中包含很多专门针对食物的属性和方法。在这种情况下,我们可将这些属性和方法提取出来,放到另一个名 为Food的类中,并将一个Food实例用作FashionRestaurant类的一个属性:

class  Restaurant():
   #skip
class Food():
    def __init__(self,food_kinds=100):
        self.food_kinds = food_kinds  #初始化食物种类的属性

    def describe_food_kind(self):
        print("This restaurant has  " + str(self.food_kinds) + " kinds of food")  #d打印一条食物种类的消息

class FashinRestaurant(Restaurant):
    def __init__(self,name,type):
        super().__init__(name,type)
        self.kinds = Food()
  
fRestaurant = FashinRestaurant('Vienna','Western')
print(fRestaurant.describe_restaurant())
fRestaurant.kinds.describe_food_kind()

以上代码定义了一个名为Food的新类,它没有继承任何类。方法__init__() 除self 外,还有另一个形参food_kinds。这个形参是可选的:如果没有给它提供值,将可自己设置。方法describe_food_kind() 也移到了这个类中。 在FashionRestaurant类中,添加了一个名为self.kinds的属性。这行代码让Python创建一个新的Food实例(由于没有指定数量,因此为默认值100 ),并将该实例存储在属性self.kinds中。每当方法__init__() 被调用时,都将执行该操作;因此现在每个FashionRestaurant实例都包含一个自动创建的Food实例。

创建一个实例,并将其存储在变量fRestaurant中。要描述食物种类时,需要使用时尚餐馆的属性kinds。以上最后一行代码是让Python在实例fRestaurant中查找属性kinds,并对存储在该属性中的Food实例调用方法describe_food_kind() 。 

输出与之前相同:

This restaurant has  100 kinds of food

还可以在Food类中添加一些公共的属性及方法。

4、导入类

随着不断地给类添加功能,文件可能变得很长,即便妥善地使用了继承亦如此。为遵循Python的总体理念,应让文件尽可能整洁。Python允许可将类存储在模块中,然后在主程序中导入所需的模块。

导入单个类:

创建一个只包含Restaurant类的模块。将它存储在一个名为restaurant.py的模块中。下面是模块restaurant.py,其中只包含Restaurant类的代码:

restaurant.py

# 一个可用于表示餐馆的类
class  Restaurant():
    def __init__(self,name,type):
        self.name = name   #初始化属性restaurant_name和cuisine_type
        self.type = type
        self.number_served = 0
    def describe_restaurant(self):
        print("The Reataurant's name is "+self.name)
        print("The cuisine's type is "+self.type)
    def open_restaurant(self):
        print("The restaurant is opening")
    def set_number_served(self,num):
        self.number_served = num
        print("It has served  " + str(self.number_served))

接下来创建另一个文件——my_restaurant.py,在其中导入Restaurant类并创建其实例:

from restaurant import Restaurant
my_restaurant = Restaurant('McDonald','Fast food')
my_restaurant .describe_restaurant()

my_restaurant.set_number_served(50)

import 语句让Python打开模块restaurant,并导入其中的Restaurant类。这样就可以使用Restaurant类了,就像它是在这个文件中定义的一样。输出与我们在前面看到的一样:

The Reataurant's name is McDonald
The cuisine's type is Fast food
It has served  50

在一个模块中存储多个类:

虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类。

也可根据需要在程序文件中导入任意数量的类。如果要在同一个程序中创普通餐馆车和时尚餐馆,就需要将Restaurant和FashionRestaurant类都导入:

from restaurant import Restaurant,FashionRestaurant

也可直接导入整个restaurant模块:

import restaurant

my_restaurant = restaurant.Restaurant('dt','snack')

导入模块中的所有类,可使用下面语法:

from module_name import *

需要从一个模块中导入很多类时,最好导入整个模块,并使用 module_name.class_name 语法来访问类。这样做时,虽然文件开头并没有列出用到的所有类,但你清楚地知 道在程序的哪些地方使用了导入的模块;你还避免了导入模块中的每个类可能引发的名称冲突。

5、Python标准库

Python标准库 是一组模块,安装的Python都包含它。可使用标准库中的任何函数和类,为此只需在程序开头包含一条简单的import 语句。

如果想使用模块collections 中的一个类——OrderedDict 。在代码前需导入:

from collections import OrderedDict

6、类编码风格

类名应采用驼峰命名法 ,即将类名中的每个单词的首字母都大写,而不使用下划线。

实例名和模块名都采用小写格式,并在单词之间加上下划线。 对于每个类,都应紧跟在类定义后面包含一个文档字符串。

这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。每个模块也都应包含一个文档字符串,对其中的类可用于做什么进行描述。 可使用空行来组织代码,但不要滥用。

在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。

需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的import 语句,再添加一个空行,然后编写导入你自己编写的模块的import 语句。在包含多条import 语句的程序中,这种做法让人更容易明白程序使用的各个模块都来自何方。