本章内容来自书籍,记录下来仅方便复习,如有侵权,请联系作者删除。

面向对象编程是最有效的软件编写方法之一。在面向对象编程中,可以编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。

一、创建和使用类

编写一个表示小狗的简单类Dog,它表示的不是特定的小狗,而是任何小狗。有名字,年龄,会蹲下,会打滚。
1. 创建Dog类

class Dog():
    """一次模拟小狗的简单尝试"""
    def __init__(self,name,age):
        """初始化name和age"""
        self.name = name
        self.age = age

    def sit(self):
        """模拟小狗被命令时蹲下"""
        print(self.name.title() + " is now sitting.")

    def roll_over(self):
        """模拟小狗被命令时打滚"""
        print(self.name.title() + " rolled over!")

根据约定,在Python中,首字母大写的名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。其中的文档字符串,对这个类的功能作了描述。

(1)方法__init__(self,name,age)类中的函数称为方法,函数与方法唯一重要的差别就是调用的方式不同。初始化方法中前后各有两个下划线"__",这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。

方法__init()__,包含三个形参:self、name、age。

  • 形参self:形参self必须位于其他形参的前面,每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
  • 形参name、age:我们创建实例时,Python将调用Dog类中的方法__init__(),此时self会自动传递,只需要给自定义的形参name和age提供值。

以方法体中,变量name和age都有self为前缀,表示该变量可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。 self.name = name获取存储在形参name中的值,并将其存储到变量name中,然后该变量被关联到当前创建的实例。像这样可以通过实例访问的变量称为属性

(2)方法sit(self)和roll_over(self),由于这些方法不需要额外信息,因此它们只有一个形参self,我们后面创建的实例都能够访问这些方法。

(3)在Python2.7中创建类
需要在创建类的括号中,添加单词object,语法如下:

class ClassName(object):
	--snip--

2. 根据类创建实例
下面来创建一个表示特定小狗的实例

class Dog():
	--sinp--
	
my_dog = Dog("willie",6)

创建实例时,Python使用参数‘’willie"和"6"调用Dog类中的方法__init__(),方法__init__()创建实例,并使用我们提供的值来设置属性name和age。并自动返回一个表示这条小狗的实例。我们将这个实例存储在变量my_dog中。

我们通常可以认为首字母大写的名称指的是类,而小写名称指的是根据类创建的实例。

(1)访问属性
要访问实例的属性,可使用句点表示法。
访问规则:实例.属性

class Dog():
	--sinp--
	
my_dog = Dog("willie",6)
print(my_dog.name)

在这里,Python先找到实例my_dog,再查找与这个实例相关的属性name。在Dog类中引用这个属性时,使用的是self.name

(2)调用方法
根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法。
访问规则:实例.方法

class Dog():
	--sinp--
	
my_dog = Dog("willie",6)
my_dog.sit()

(3)创建多个实例
可按需求根据类创建任意数量的实例,条件是讲每个实例都存储在不同的变量中,或者占用列表或字典的不同位置。

class Dog():
	--sinp--
	
my_dog = Dog("willie",6)
your_dog = Dog("lucy",3)

二、使用类和实例

编写好类后,我们将花大部分时间在根据类创建的实例上。修改实例的属性:直接根据实例调用属性进行修改;也可以编写方法,在方法中调用属性修改。

1. Car类

下面来编写一个表示汽车的类,它存储了有关汽车的信息(make制造商,model型号,year生产年份)。

class Car():
    """一次模拟汽车的简单尝试"""
    
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year

    def get_car_info(self):
        """返回整洁的描述性信息"""
        car_info = str(self.year) + " " + self.make + " " + self.model
        return car_info.title()

2. 给属性指定默认值
类中的每个属性都必须有初始值,哪怕这个值是0或者空字符串。
在有些情况下,设置默认值时,可以在方法__init__()中设置初始值,无需提供该初始值的形参。

如下所示:在方法__init__()中设置属性,指定默认值,并定义一个方法read_odometer()调用该属性的值。

class Car():

    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        --sinp--
        self.odometer_reading = 0

    def read_odometer(self):
        print("里程:" + self.odometer_reading)

3. 修改属性的值

(1)创建实例,直接修改属性的值

class Car():
    """一次模拟汽车的简单尝试"""

    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def get_car_info(self):
        """返回整洁的描述性信息"""
        car_info = str(self.year) + " " + self.make + " " + self.model
        return car_info.title()

    def read_odometer(self):
        print("里程:" + str(self.odometer_reading))

my_new_car = Car('audi','a4',2016)
my_new_car.odometer_reading = 22
print("我的新车:"+ my_new_car.get_car_info())
my_new_car.read_odometer()

运行结果
我的新车:2016 Audi A4
里程:22

(2)通过方法修改属性的值
在方法中调用属性并传值,在实例中调用该方法并传值。

def update_odometer(self,mileage):
        self.odometer_reading = mileage

my_new_car = Car('audi','a4',2016)
my_new_car.update_odometer(22)

(3)通过方法对属性的值进行递增
同(2)

三、继承

编写类时,并非总是要从空白开始。一个类继承另一个类时,原有的类称为父类,而新类称为子类。子类继承了父类的所有属性和方法,同时还可以定义自己的属性和方法。

1. 子类的方法__init__()
定义子类时:

  • 父类的代码必须包含在当前文件中,且位于子类的前面;
  • 必须在括号内指定父类的名称;
  • 方法__init__()接受创建Car实例所需的信息。
  • 在初始化函数中,使用函数super(),调用父类的方法__init__(),让子类实例包含父类的所有属性。父类称为超类(superclass),名称super因此而得名。

创建子类的实例时:

  • Python首先需要完成的任务是给父类的所有属性赋值。
class Car():
	def __init__(self,make,model,year):	
		--snip--

class ElectricCar(Car):
    """电瓶车,继承Car类"""
    
    def __init__(self,make,model,year):
        """初始化父类的属性"""
        super().__init__(make,model,year)
        --snip--

my_testla = ElectricCar('testla','model s',2016)

2. Python2.7中的继承
函数super()需要两个实参,子类名和对象self。在Python2.7中使用继承时,务必在定义父类时在括号内指定object。

class Car(object):
	def __init__(self,make,model,year):
		--snip--

class ElectricCar(Car):
	def __init__(self,make,model,year):
		super(ElectricCar,self).__init__(make,model,year)
			--snip--

3. 给子类定义属性和方法
让一个类继承另一个类后,可添加新的属性和方法。
如下所示,ElectricCar继承Car类后,添加新属性battery_size和describe_battery()。

class Car():
	--snip--
	
class ElectricCar(Car):
    """电瓶车,继承Car类"""

    def __init__(self,make,model,year):
        """初始化父类的属性"""
        super().__init__(make,model,year)
        self.battery_size = 70

    def describe_battery(self):
        print("电瓶容量: " + self.battery_size)

4. 重写父类的方法

子类中的方法与父类中的方法同名,用子类的实例调用该方法时,Python只识别子类中的方法。

class Car():
	--snip--
	
class ElectricCar(Car):
    """电瓶车,继承Car类"""

    def __init__(self,make,model,year):
        """初始化父类的属性"""
        super().__init__(make,model,year) 

    def get_car_info(self):
        """返回整洁的描述性信息"""
        car_info = str(self.year) + "------>" + self.make
        return car_info.title()
        
my_electric_car = ElectricCar("aimashi","","2018")
print(my_electric_car.get_car_info())

使用继承时,可让子类保留从父类那里继承而来的精华,并剔除不需要的糟粕。

5. 将实例用作属性

在类的初始化方法中,创建一个属性,新建一个实例保存在该属性中。每当该类的方法__init__()被调用时,都将执行该操作。

如下所示:
(1)把电瓶车的特性取出来,封装到新类Battery中,并定义属性battery_size和方法describe_battery()
(2)在ElectricCar的初始化方法中,添加一个名为self.battery的属性,用于存储新建的Battery实例。
(3)每当创建ElectricCar实例时,都会调用__init__()方法,自动创建Battery的实例。
(4)可以用ElectricCar实例调用Battery类中的方法,调用方式为:ElectricCar实例.存储Battery实例的属性名.Battery类中的方法

class Car():
	--snip--

class Battery():
    """模拟电动汽车电瓶"""
    def __init__(self,battery_size = 70):
        self.battery_size = battery_size

    def describe_battery(self):
        print("电瓶容量: " + str(self.battery_size))

class ElectricCar(Car):
    """电瓶车,继承Car类"""

    def __init__(self,make,model,year):
        """初始化父类的属性"""
        super().__init__(make,model,year)
        # self.battery_size = 70
        self.battery = Battery()

    def get_car_info(self):
        """返回整洁的描述性信息"""
        car_info = str(self.year) + "------>" + self.make
        return car_info.title()

my_electric_car = ElectricCar("aimashi","","2018")
my_electric_car.battery.describe_battery()

6. 模拟实物
模拟叫复杂的物件时,需要从较高的逻辑层面思考问题,非语法层面。现实世界的建模没有对错之分,要找出效率更高的表示法,需要经过一定的实践,如用不同的方式重写类。

四、导入类

1. 导入单个类

from car import Car

这里的import语句让Python打开模块car并导入其中的Car类。

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

如下,car模块中包含两个类,Car类和ElecaricCar类。

car.py

class Car():
	--snip--

class ElectricCar(Car):
	--snip--

3. 从一个模块中导入多个类
可以根据需要在程序文件中导入任意数量的类,用逗号隔开各个类。可以根据需要,为每个类创建任意数量的实例。

from car import Car, ElectricCar

4. 导入整个模块
导入整个模块,使用句点表示法访问需要的类。这种导入方式很简单,代码也易于阅读。由于创建类实例的代码都包含模块名,因此不会与当前文件使用的任何名称发生冲突。

import car

my_beetle = car.Car('volkswagen','beetle',2016)
my_tesla = car.ElectricCar('tesla','roadster',2017)

5. 导入模块中的所有类(不建议使用)
要导入模块中的每个类,语法如下

from module_name import *

不推荐使用,原因有下:
(1)这种方式没有明确指出你使用了模块中的那些类,会引发困惑;
(2)当导入的类与你程序的其他类同名时,会引发难以诊断的错误。

6. 在一个模块中导入另一个模块
一个模块的类依赖另一个模块的类。

模块car中的类Car

car.py
class Car():
	--snip--

模块electric_car中的类ElectricCar依赖类Car

electric_car.py

from car import Car

class Bettery():
	--snip--

class ElectricCar(Car):
	--snip--

导入car和electric_car

my_car.py
from car import Car
from electric_car import ElectricCar

my_beetle = Car('volkswagen','beetle',2016)
my_testla = ElectricCar('tesla','roadster',2017)

7. 自定义工作的流程
一开始应该让代码结构尽可能简单,尽量先在一个文件中完成所有的工作,确定一切都能正常运行了之后,再将类移到独立的模块中。

五、Python标准库

Python标准库是一组模块,安装的Python都包含它。

六、类编码风格

  • 命名:类名应采用驼峰命名法,即将类名中的每个单词的首字母都大写,而不用下划线。实例名和模块名都应采用小写格式,并在单词之间加上下划线。
  • 文档字符串:对于每个类,都应在类定义后面添加一个文档字符串,简要描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。每个模块也应包含一个文档字符串,对其中的类可用于做什么进行描述。
  • 空行:可用空行来组织代码,当不要滥用。在类中,使用一个空行隔开方法;在模块中,使用两个空行来隔开类。
  • 导入两种类:需要同时导入标准库中的模块和你编写的模块是,先编写导入标准库模块的import语句,再添加一个空行,然后编写你自己的模块的import语句。

七、小结

学习了编写类,在类中存储信息,编写方法,根据类创建包含所需属性的实例。修改属性,继承,将一个类的实例作另一个类的属性。类导入,Python标准库。