Python个人学习笔记(8)——类

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

创建和使用类

使用类几乎可以模拟任何东西

  • 创建Dog类
    根据Dog类创建的每个示例都将存储名字和年龄.我们赋予了每条小狗蹲下(sit())和打滚(roll_over())的能力
    dog.py

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!")

#创建实例
my_dog = Dog('willie',6)
#1.访问属性
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
#2.调用方法
my_dog.sit()
my_dog.roll_over()
#3.创建多个实例
your_dog = Dog('lucy',3)
#1.访问属性
print("Your dog's name is " + your_dog.name.title() + ".")
print("Your dog is " + str(your_dog.age) + " years old.")

运行结果:
My dog’s name is Willie.
My dog is 6 years old.
Willie is now sitting.
Willie rolled over!
Your dog’s name is Lucy.
Your dog is 3 years old.
例子如上,让我们来细细解析这个例子.

  • Python中,首字母大写的名称指的是类.这个类的定义是空的,因为我们要从空白创建这个类.
  • 类中的函数称为方法;前面学到的有关函数的一切都适用于方法,就目前而言,唯一重要的差别是调用函数的方式.第一个方法__init__()方法是一个特殊的方法,每当根据类创建新实例时,Python都会自动运行它.在这个方法的名称中,开头和结尾各有两个下划线,这是一种约定,旨在避免Python默认方法和普通方法发生冲突.
    我们将方法__init__()定义成了包含三个形参:self、name和age。在这个方法的定义中,形参self必不可少,还必须位于其他实参前面。Python调用这个__init__方法来创建实例时,将自动传入实参。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。我们通过实参向类传递实参时,self会自动传递,因此我们不需要传递它。在上面的例子中,当我们根据Dog()创建实例时,都只需给最后两个形参提供值。
  • 初始化属性时,定义的变量都要有前缀self。以self为前缀的变量都可以供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。例子中的self.name = name获取存储在形参name中的值,并将其存储到变量name中,然后该变量被关联到当前创建的实例.self.age = age的作用与此类似.像这样可通过实例访问的变量称为属性.
  • Dog类还定义了另外两个方法:str()和roll_over().由于这些方法都不需要额外的信息,因此它们只有一个形参self.我们后面的实例能够访问这些方法.目前,sit()和roll_over所做的有限,后续还可以扩展这些方法以模拟实际情况.
  • 访问属性,对应上面注释处.要访问实例的属性,可使用句点表示法.
  • 调用方法,对应上面注释处.根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法.
  • 创建多个实例,对应上方注释处.可按需求根据类创建任意数量的实例.即使两次创建的实例name和age都相同,Python还是会根据Dog类创建另一个实例.可按需求根据一个类创建任意数量的实例,条件是将每个实例都存储在不同的变量中,或占用列表或字典的不同位置.

使用类和实例

你可以使用类来模拟现实世界的很多情景.类编写好后,你的大部分时间都将花在使用根据类创建的实例上.你需要执行的一个重要任务是修改实例的属性.你可以直接修改实例的属性.也可以编写方法以特定的方式进行修改.

  • 创建Car类
    下面来编写一个表示汽车的类,它存储了有关汽车的信息,还有几个方法
    Car.py

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

    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        #给属性指定默认值
        self.odometer_reading = 0


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


    def read_odometer(self):
        """打印一条指出汽车里程的消息"""
        print("This car has " + str(self.odometer_reading) + " miles on it.")

    
    def update_odometer(self,mileage):
        """将里程表读数设置为指定的值"""
        #防止错误的修改
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("Your can't roll back an odometer!")


    def increase_odometer(self,miles):
        """将里程表读数增加指定的量"""
        #防止错误的修改
        if miles >= 0:
            self.odometer_reading += miles
        else:
            print("Your can't roll back an odometer!")


my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
#修改属性的值
#1.直接修改属性的值
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
#2.通过方法修改属性的值
my_new_car.update_odometer(23500)
my_new_car.read_odometer()
#3.通过方法对属性进行递增
my_new_car.increase_odometer(100)
my_new_car.read_odometer()

运行结果:
2016 Audi A4
This car has 0 miles on it.
This car has 23 miles on it.
This car has 23500 miles on it.
This car has 23600 miles on it.
例子如上,让我们来细细解析这个例子.

  • 类中的每个属性都必须有初始值,哪怕这个值是0或空字符.在有些情况下,在方法__init__()指定这种初始值是可行的;如果你对某个属性这样做了,就无需包含为它提供初始值的实参.正如下方代码:
self.odometer_reading = 0
  • 要修改属性的值,最简单的方式就是通过实例直接访问它,正如下方代码:
my_new_car.odometer_reading = 23
  • 如果有替你更新属性的代码,你将无需直接访问属性,而可将值传递给一个方法,由它在内部进行更新.正如下方代码:
def update_odometer(self,mileage):
        """将里程表读数设置为指定的值"""
        #防止错误的修改
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("Your can't roll back an odometer!")

my_new_car.update_odometer(23500)
  • 有时需要将属性值递增特定的量,而不是将其设置为全新的值.正如下方代码:
def increase_odometer(self,miles):
        """将里程表读数增加指定的量"""
        #防止错误的修改
        if miles >= 0:
            self.odometer_reading += miles
        else:
            print("Your can't roll back an odometer!")

my_new_car.increase_odometer(100)

注意:你可以使用类似于上面的方法来控制用户修改属性值的方式,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值.要确保安全,除了进行类似上面的基本检查外,还需特别注意细节.

继承

编写类时,并非总是要从空白开始.如果你要编写的类是另一个现成类的特殊版本,可使用继承.一个类继承另一个类时,它将自动获得另一个类的全部属性和方法,同时还可以定义自己的属性和方法.

  • 子类的方法__init__()
    创建子类的实例时,python首先要完成的任务是给父类的所有属性赋值.为此,子类的方法需要父类施以援手.
    例如,下面来模拟电动汽车.电动汽车是一种特殊的汽车,因此我们可以在前面创建的Car类的基础上创建一个简单的ElectricCar,因此我们只需为电动汽车特有的属性和行为编写代码.
    ElectricCar.py

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

    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        #给属性指定默认值
        self.odometer_reading = 0


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


    def read_odometer(self):
        """打印一条指出汽车里程的消息"""
        print("This car has " + str(self.odometer_reading) + " miles on it.")

    
    def update_odometer(self,mileage):
        """将里程表读数设置为指定的值"""
        #防止错误的修改
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("Your can't roll back an odometer!")


    def increase_odometer(self,miles):
        """将里程表读数增加指定的量"""
        #防止错误的修改
        if miles >= 0:
            self.odometer_reading += miles
        else:
            print("Your can't roll back an odometer!")


class Battery():
    """一次模拟电动汽车电瓶的简单尝试"""

    def __init__(self,battery_size=70):
        """打印一条描述电瓶容量的消息"""
        self.battery_size = battery_size


    def describe_battery(self):
        """打印一条关于电瓶的消息"""
        print("This car has a " + str(self.battery_size) + "-kwh battery.")


    def get_range(self):
        """打印一条消息,指出电瓶车的续航里程"""
        if self.battery_size == 70:
            range = 240
        elif self.battery == 85:
            range = 270
        
        message = "This car can go approximately " + str(range)
        message += "miles on a full charge."
        print(message)


class ElectricCar(Car):
    """电动汽车的独特之处"""
    
    def __init__(self,make,model,year):
        """
        初始化父类的属性
        初始化父类的属性,再初始化电动汽车特有的属性
        """
        super().__init__(make,model,year)
        self.battery = Battery()

    def fill_gas_tank(self):
        """电动汽车没有油箱"""
        print("This car doesn't need a gas tank!")
    

my_tesla = ElectricCar('tesla','model s',2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()

运行结果:
2016 Tesla Model S
This car has a 70-kwh battery.
This car can go approximately 240miles on a full charge.
例子如上,让我们来细细解析这个例子.

  • 首先是父类的代码.创建子类时,父类必须包含再当前文件夹中,且位于子类前面
  • 定义子类时,必须在括号中指定父类的名称.如下方代码:
class ElectricCar(Car):
  • super()函数是一个特殊函数,帮助Python将父类和子类连接起来.父类也被称为superclass,名称super由此得名.下方代码使得Python调用子类的父类的方法,让子类包含父类的所有属性.
super().__init__(make,model,year)

此时,电动汽车有了汽车的一般属性

  • 给子类定义属性和方法,在类ElectricCar中的__init__()方法中添加以下代码:
self.battery_size = 70

这种定义方式使用于比较少的属性

  • 将实例当做属性,使用代码模拟实物时,你可能发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长.这时候,可能需要将类的一部分作为一个单独的类提取出来.就拿电瓶来说:
    在类ElectricCar中:
self.battery = Battery()

在类Battery中:

class Battery():
    """一次模拟电动汽车电瓶的简单尝试"""

    def __init__(self,battery_size=70):
        """打印一条描述电瓶容量的消息"""
        self.battery_size = battery_size


    def describe_battery(self):
        """打印一条关于电瓶的消息"""
        print("This car has a " + str(self.battery_size) + "-kwh battery.")


    def get_range(self):
        """打印一条消息,指出电瓶车的续航里程"""
        if self.battery_size == 70:
            range = 240
        elif self.battery == 85:
            range = 270
        
        message = "This car can go approximately " + str(range)
        message += "miles on a full charge."
        print(message)

这看似做了很多额外的工作,但现在我们想多详细描述电瓶都行.

个人认为:这种做法相当于前面的函数,一个个各司其职

  • 重写父类的方法.对于父类的方法,只要它不符合子类的需要,都可以对齐进行重写.拿油箱来说,假如Car类中有一个fill_gas_tank()的方法,但是电瓶车没有油箱,那么,我们可以对它进行重写:
def fill_gas_tank(self):
        """电动汽车没有油箱"""
        print("This car doesn't need a gas tank!")

现在,如果有人对电动汽车调用方法fill_gas_tank(),Python将忽略Car类中的方法fill_gas_tank(),转而运行上述代码.

导入类

随着不断地给类添加功能,文件可能变得很长,即便你妥善地使用了继承亦如此.为遵循Python的主题理念,应让文件尽可能整洁.为在这方面提供帮助,Python蕴蓄你将类存储在模块中,然后在主程序中导入所需的模块.
假如说,前面的三个类都存储在一个car.py文件中,然后,我们要在my_car.py文件从car.py导入类,有:

  • 导入单个类:

from car import Car

导入多个类:


- from car import Car,ElectricCar,Battery

可根据需要导入任意数量的类

  • 在一个模块中存储多个类
    正如前面所说的"三个类都存储在一个car.py文件中".虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类
  • 导入整个模块,然后用句点表示法访问:
import module_name
  • 导入一个模块中的所有类
from module_name import *

不需要使用句号表示法即可访问,但不推荐使用.因为,可能导入与当前文件中同名的类,从而因此名称冲突.

  • 在一个模块中导入另一个模块
    有时候,需要将类分散到多个模块中,以免模块太大,或在同一个模块存储不相关的类.将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类.在这种情况下,可在前一个模块中导入必要的类.

将Car类存储在car.py中,ElectricCar类存储在electric_car.py中.
引用时:


from car import Car
from electric_car import ElectricCar

程序可正常运行

  • 自定义工作原理
    一开始应让代码尽可能简单.先可能在一个文件中完成所有的工作,确定一切都能正常运行后,再将类移到独立的模块中.

Python标准库

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

还可以从其他地方下载外部模块.比如:使用pip.

类编程风格

  • 类名应使用驼峰命名法,即类名中的每个单词的首字母都大写,而不使用下划线.
  • 实例名和模块名都应使用小写格式,并在单词之间加上下划线.
  • 对于每个类,都应紧跟在类定义之后包含一个文档字符串.

这种字符串简要地描述类的功能,并遵循编写字符串时采用的格式约定.每个模块也都应包含一个文档字符串,对其中的类可做什么进行描述.


  • 在类中,使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类.
  • 需要同时导入标准库中的模块和自己编写的模块时,先编写导入标准库的import语句,再添加一个空行,然后编写导入自己编写的import语句

个人从书本学到的知识,作为自己学习笔记的同时,与各位朋友分享,如有不足,请多多指教

参考文献:《Python编程从入门到实践》【美】Eric Matthes 著 袁国忠 译

你可能感兴趣的:(Python个人学习笔记,python,编程语言,类)