当前位置:网站首页>9 - 类

9 - 类

2022-07-04 22:17:00 axinawang

使用面向对象编程可模拟现实情景。

根据类来创建对象称为实例化。

9.1 创建和使用类

使用类几乎可以模拟任何东西。下面来编写一个表示小狗的简单类Dog,这个类让Python知道如何创建表示小狗的对象。

9.1.1 创建Dog类

class Dog:    #class定义类,其后就是类名,根据约定,在Python中,类名首字母要大写。
    """创建小狗模型"""    #文档字符串,对这个类的功能做了描述。
  
    def __init__(self, name, age):
        """初始化小狗的姓名和年龄属性"""
        self.name = name
        self.age = age
        
    def sit(self):
        """模拟小狗收到命令时蹲下."""
        print(f"{self.name} is now sitting.")

    def roll_over(self):
        """模拟小狗收到命令时打滚."""
        print(f"{self.name} rolled over!")

类中的函数称为方法。

方法__init__()是一个特殊方法,每当你根据Dog类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。在这个方法的定义中,形参self必不可少,而且必须位于其他形参的前面。每个与实例相关联的方法调用都自动传递实参self。每个与实例相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用。 

self.变量 , 该变量可供类中的所有方法使用,该变量被关联到当前创建的实例。像这样可通过实例访问的变量称为属性。

Dog类还定义了另外两个方法:sit()和roll_over()。

9.1.2 根据类创建实例

可将类视为有关如何创建实例的说明。

my_dog = Dog('Willie', 6)

Python使用实参'Willie'和6调用Dog类的方法__init__(),该方法创建一个表示特定小狗的实例,并使用提供的值来设置属性name和age。

9.1.3 访问属性

通过实例访问属性:实例的名称.属性。

在类中引用属性:self.name。

例如:输出有关my_dog的摘要:

print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")

9.1.4 调用方法

通过实例调用方法:实例的名称.方法。

my_dog.sit()
my_dog.roll_over()

遇到代码my_dog.sit()时,Python在类Dog中查找方法sit()并运行其代码。 

9.1.5 创建多个实例

my_dog = Dog('Willie', 6)
your_dog = Dog('Lucy', 3)

print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
my_dog.sit()

print(f"\nYour dog's name is {your_dog.name}.")
print(f"Your dog is {your_dog.age} years old.")
your_dog.sit()

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

动手试一试

练习9-1:餐馆

创建一个名为Restaurant的类,为其方法__init__()设置属性restaurant_name和cuisine_type。创建一个名为describe_restaurant()的方法和一个名为open_restaurant()的方法,前者打印前述两项信息,而后者打印一条消息,指出餐馆正在营业。根据这个类创建一个名为restaurant的实例,分别打印其两个属性,再调用前述两个方法。

练习9-2:三家餐馆

根据为完成练习9-1而编写的类创建三个实例,并对每个实例调用方法describe_restaurant()。

练习9-3:用户

创建一个名为User的类,其中包含属性first_name和last_name,以及用户简介通常会存储的其他几个属性。在类User中定义一个名为describe_user()的方法,用于打印用户信息摘要。再定义一个名为greet_user()的方法,用于向用户发出个性化的问候。创建多个表示不同用户的实例,并对每个实例调用上述两个方法。

9.2 使用类和实例

9.2.1 Car类

class Car:
    """模拟汽车"""

    def __init__(self, make, model, year):
        """初始化属性"""
        self.make = make
        self.model = model
        self.year = year
        
    def get_descriptive_name(self):
        """返回汽车的具体名称"""
        long_name = f"{self.year} {self.make} {self.model}"
        return long_name.title()

9.2.2 给属性指定默认值

创建实例时,有些属性无须通过形参来定义,可在方法__init__()中为其指定默认值。

下面来添加一个名为odometer_reading的属性,其初始值总是为0。我们还添加了一个名为read_odometer()的方法,用于读取汽车的里程表:

class Car:

    def __init__(self, make, model, year):
        """Initialize attributes to describe a car."""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0    #新添加的属性,代表汽车总里程
        
    def read_odometer(self):
        """Print a statement showing the car's mileage."""
        print(f"This car has {self.odometer_reading} miles on it.")

9.2.3 修改属性的值

我们能以三种方式修改属性的值:

1、直接通过实例进行修改

my_used_car.odometer_reading=10

2、通过方法进行设置

def update_odometer(self, mileage):
        """将里程表读数设置为指定的值"""
        self.odometer_reading = mileage
        

下面来添加一些逻辑,禁止任何人将里程表读数往回调:

def update_odometer(self, mileage):
        """
        更新里程数.
        禁止回调里程数.
        """
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("警告:禁止回调里程数!")

 现在,update_odometer()在修改属性前检查指定的读数是否合理。如果新指定的里程(mileage)大于或等于原来的里程(self.odometer_reading),就将里程表读数改为新指定的里程;否则发出警告,指出不能将里程表往回调。

3、通过方法进行递增(增加特定的值)。

def increment_odometer(self, miles):
        """将里程数增加指定的量."""
        self.odometer_reading += miles

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

动手试一试

练习9-4:就餐人数

在为完成练习9-1而编写的程序中,添加一个名为number_served的属性,并将其默认值设置为0。根据这个类创建一个名为restaurant的实例。打印有多少人在这家餐馆就餐过,然后修改这个值并再次打印它。添加一个名为set_number_served()的方法,让你能够设置就餐人数。调用这个方法并向它传递一个值,然后再次打印这个值。添加一个名为increment_number_served()的方法,让你能够将就餐人数递增。调用这个方法并向它传递一个这样的值:你认为这家餐馆每天可能接待的就餐人数。

练习9-5:尝试登录次数

在为完成练习9-3而编写的User类中,添加一个名为login_attempts的属性。编写一个名为increment_login_attempts()的方法,将属性login_attempts的值加1。再编写一个名为reset_login_attempts()的方法,将属性login_attempts的值重置为0。

根据User类创建一个实例,再调用方法increment_login_attempts()多次。打印属性login_attempts的值,确认它被正确地递增。然后,调用方法reset_login_attempts(),并再次打印属性login_attempts的值,确认它被重置为0。

9.3 继承

如果要编写的类是另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为子类。子类继承了父类的所有属性和方法,同时还可以定义自己的属性和方法。

9.3.1 子类的方法__init__()

在既有类的基础上编写新类时,通常要调用父类的方法__init__()。

class Car:
    """A simple attempt to represent a 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 = f"{self.year} {self.make} {self.model}"
        return long_name.title()
    
    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it.")
        
    def update_odometer(self, mileage):
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")
    
    def increment_odometer(self, miles):
        self.odometer_reading += miles

class ElectricCar(Car):
    """电动汽车"""
    
    def __init__(self, make, model, year):
        """初始化父类的属性"""
        super().__init__(make, model, year)

my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())

 创建子类时,父类必须包含在当前文件中,且位于子类前面。

super()是一个特殊函数,让你能够调用父类的方法。

父类也称为超类(superclass),名称super由此而来。

9.3.2 给子类定义属性和方法

下面来添加一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法。

class ElectricCar(Car):
    """Represent aspects of a car, specific to electric vehicles."""
    
    def __init__(self, make, model, year):
        """
        Initialize attributes of the parent class.
        Then initialize attributes specific to an electric car.
        """
        super().__init__(make, model, year)
        self.battery_size = 75

    def describe_battery(self):
        """Print a statement describing the battery size."""
        print(f"This car has a {self.battery_size}-kWh battery.")

my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()

9.3.3 重写父类的方法

对于父类的方法,只要它不符合子类模拟的实物的行为,都可以进行重写。为此,可在子类中定义一个与要重写的父类方法同名的方法。

假设Car类有一个名为fill_gas_tank()的方法,它对全电动汽车来说毫无意义,因此你可能想重写它。

def fill_gas_tank(self):
        print("纯电动汽车不需要加油!")

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

9.3.4 将实例用作属性

可以将大型类拆分成多个协同工作的小类。

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

class Battery:
    """A simple attempt to model a battery for an electric car."""
    
    def __init__(self, battery_size=75):
        """Initialize the battery's attributes."""
        self.battery_size = battery_size

    def describe_battery(self):
        """Print a statement describing the battery size."""
        print(f"This car has a {self.battery_size}-kWh battery.")

class ElectricCar(Car):
    """Represent aspects of a car, specific to electric vehicles."""
    
    def __init__(self, make, model, year):
        """
        Initialize attributes of the parent class.
        Then initialize attributes specific to an electric car.
        """
        super().__init__(make, model, year)
        self.battery = Battery()

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

 下面再给Battery类添加一个方法,它根据电瓶容量报告汽车的续航里程:

    def get_range(self):
        """Print a statement about the range this battery provides."""
        if self.battery_size == 75:
            range = 260
        elif self.battery_size == 100:
            range = 315
            
        print(f"This car can go about {range} miles on a full charge.")

9.3.5 模拟实物

模拟较复杂的物件(如电动汽车)时,需要解决一些有趣的问题。续航里程是电瓶的属性还是汽车的属性呢?如果只描述一辆汽车,将方法get_range()放在Battery类中也许是合适的,但如果要描述一家汽车制造商的整个产品线,也许应该将方法get_range()移到ElectricCar类中。在这种情况下,get_range()依然根据电瓶容量来确定续航里程,但报告的是一款汽车的续航里程。也可以这样做:仍将方法get_range()留在Battery类中,但向它传递一个参数,如car_model。在这种情况下,方法get_range()将根据电瓶容量和汽车型号报告续航里程。这让你进入了程序员的另一个境界:解决上述问题时,从较高的逻辑层面(而不是语法层面)考虑;考虑的不是Python,而是如何使用代码来表示实物。达到这种境界后,你会经常发现,对现实世界的建模方法没有对错之分。有些方法的效率更高,但要找出效率最高的表示法,需要经过一定的实践。只要代码像你希望的那样运行,就说明你做得很好!即便发现自己不得不多次尝试使用不同的方法来重写类,也不必气馁。要编写出高效、准确的代码,都得经过这样的过程。

动手试一试

练习9-6:冰激凌小店

冰激凌小店是一种特殊的餐馆。编写一个名为IceCreamStand的类,让它继承为完成练习9-1或练习9-4而编写的Restaurant类。这两个版本的Restaurant类都可以,挑选你更喜欢的那个即可。添加一个名为flavors的属性,用于存储一个由各种口味的冰激凌组成的列表。编写一个显示这些冰激凌的方法。创建一个IceCreamStand实例,并调用这个方法。

练习9-7:管理员

管理员是一种特殊的用户。编写一个名为Admin的类,让它继承为完成练习9-3或练习9-5而编写的User类。添加一个名为privileges的属性,用于存储一个由字符串(如"can add post"、"can delete post"、"can ban user"等)组成的列表。编写一个名为show_privileges()的方法,显示管理员的权限。创建一个Admin实例,并调用这个方法。

练习9-8:权限

编写一个名为Privileges的类,它只有一个属性privileges,其中存储了练习9-7所述的字符串列表。将方法show_privileges()移到这个类中。在Admin类中,将一个Privileges实例用作其属性。创建一个Admin实例,并使用方法show_privileges()来显示其权限。

练习9-9:电瓶升级

在本节最后一个electric_car.py版本中,给Battery类添加一个名为upgrade_battery()的方法。该方法检查电瓶容量,如果不是100,就将其设置为100。创建一辆电瓶容量为默认值的电动汽车,调用方法get_range(),然后对电瓶进行升级,并再次调用get_range()。你将看到这辆汽车的续航里程增加了。

9.4 导入类

Python允许将类存储在模块中,然后在主程序中导入所需的模块。

9.4.1 导入单个类

将Car类存储在一个名为car.py的模块,使用该模块的程序都必须使用更具体的文件名,如my_car.py。

"""A class that can be used to represent a car."""  #模块级文档字符串

class Car:
    """A simple attempt to represent a car."""

    def __init__(self, make, model, year):
        """Initialize attributes to describe a car."""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
        
    def get_descriptive_name(self):
        """Return a neatly formatted descriptive name."""
        long_name = f"{self.year} {self.make} {self.model}"
        return long_name.title()
    
    def read_odometer(self):
        """Print a statement showing the car's mileage."""
        print(f"This car has {self.odometer_reading} miles on it.")
        
    def update_odometer(self, mileage):
        """
        Set the odometer reading to the given value.
        Reject the change if it attempts to roll the odometer back.
        """
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")
    
    def increment_odometer(self, miles):
        """Add the given amount to the odometer reading."""
        self.odometer_reading += miles

 你应为自己创建的每个模块编写文档字符串。

my_car.py

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

my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())

my_new_car.odometer_reading = 23
my_new_car.read_odometer()

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

在car.py中添加电池类和电动汽车类

class Battery:
    """A simple attempt to model a battery for an electric car."""

    def __init__(self, battery_size=75):
        """Initialize the battery's attributes."""
        self.battery_size = battery_size

    def describe_battery(self):
        """Print a statement describing the battery size."""
        print(f"This car has a {self.battery_size}-kWh battery.")
        
    def get_range(self):
        """Print a statement about the range this battery provides."""
        if self.battery_size == 75:
            range = 260
        elif self.battery_size == 100:
            range = 315
            
        print(f"This car can go about {range} miles on a full charge.")
    
class ElectricCar(Car):
    """Models aspects of a car, specific to electric vehicles."""

    def __init__(self, make, model, year):
        """
        Initialize attributes of the parent class.
        Then initialize attributes specific to an electric car.
        """
        super().__init__(make, model, year)
        self.battery = Battery()

现在,可以新建一个名为my_electric_car.py的文件,导入ElectricCar类,并创建一辆电动汽车了:

from car import ElectricCar

my_tesla = ElectricCar('tesla', 'model s', 2019)

print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()

9.4.3 从一个模块中导入多个类

my_cars.py

from car import Car,ElectricCar    #用逗号分隔各个类

my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())

my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())

9.4.4 导入整个模块

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

import car    #导入整个模块

my_beetle = car.Car('volkswagen', 'beetle', 2019) #使用语法module_name.ClassName访问需要的类
print(my_beetle.get_descriptive_name())

my_tesla = car.ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())

9.4.5 导入模块中的所有类

from module_name import *

 不推荐使用这种导入方式,因为可能引发名称冲突。

9.4.6 在一个模块中导入另一个模块

下面将Car类存储在一个模块中,并将ElectricCar类和Battery类存储在另一个模块中。将第二个模块命名为electric_car.py。

from car import Car

class Battery:
    """A simple attempt to model a battery for an electric car."""

    def __init__(self, battery_size=75):
        """Initialize the battery's attributes."""
        self.battery_size = battery_size

    def describe_battery(self):
        """Print a statement describing the battery size."""
        print(f"This car has a {self.battery_size}-kWh battery.")
        
    def get_range(self):
        """Print a statement about the range this battery provides."""
        if self.battery_size == 75:
            range = 260
        elif self.battery_size == 100:
            range = 315
            
        print(f"This car can go about {range} miles on a full charge.")
    
class ElectricCar(Car):
    """Models aspects of a car, specific to electric vehicles."""

    def __init__(self, make, model, year):
        """
        Initialize attributes of the parent class.
        Then initialize attributes specific to an electric car.
        """
        super().__init__(make, model, year)
        self.battery = Battery()

现在可以分别从每个模块中导入类,以根据需要创建任何类型的汽车了:

my_cars.py

from car import Car
from electric_car import ElectricCar

my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())

my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())

9.4.7 使用别名

模块可以使用别名,导入类时,也可为其指定别名:  类名 as 别名

from electric_car import ElectricCar as EC

9.4.8 自定义工作流程

一开始应让代码结构尽可能简单。先尽可能在一个文件中完成所有的工作,确定一切都能正确运行后,再将类移到独立的模块中。如果你喜欢模块和文件的交互方式,可在项目开始时就尝试将类存储到模块中。先找出让你能够编写出可行代码的方式,再尝试改进代码。

动手试一试

练习9-10:导入Restaurant类

将最新的Restaurant类存储在一个模块中。在另一个文件中,导入Restaurant类,创建一个Restaurant实例并调用Restaurant的一个方法,以确认import语句正确无误。

练习9-11:导入Admin类

以为完成练习9-8而做的工作为基础。将User类、Privileges类和Admin类存储在一个模块中,再创建一个文件,在其中创建一个Admin实例并对其调用方法show_privileges(),以确认一切都能正确运行。练习9-12:多个模块 将User类存储在一个模块中,并将Privileges类和Admin类存储在另一个模块中。再创建一个文件,在其中创建一个Admin实例并对其调用方法show_privileges(),以确认一切依然能够正确运行。

9.5 Python标准库

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

下面来看看模块random:

        randint():它将两个整数作为参数,并随机返回一个位于这两个整数之间(含)的整数。

from random import randint

i=randint(1,6)
print(i)

        choice():它将一个列表或元组作为参数,并随机返回其中的一个元素。

>>> from random import choice
>>> list = ['a','b','c','d','e','f']
>>> choice(list)
'd'

动手试一试

练习9-13:骰子

创建一个Die类,它包含一个名为sides的属性,该属性的默认值为6。编写一个名为roll_die()的方法,它打印位于1和骰子面数之间的随机数。创建一个6面的骰子再掷10次。创建一个10面的骰子和一个20面的骰子,再分别掷10次。

练习9-14:彩票

创建一个列表或元组,其中包含10个数和5个字母。从这个列表或元组中随机选择4个数或字母,并打印一条消息,指出只要彩票上是这4个数或字母,就中大奖了。

练习9-15:彩票分析

可以使用一个循环来明白前述彩票大奖有多难中奖。为此,创建一个名为my_ticket的列表或元组,再编写一个循环,不断地随机选择数或字母,直到中大奖为止。请打印一条消息,报告执行循环多少次才中了大奖。

练习9-16:Python Module of the Week

要了解Python标准库,一个很不错的资源是网站Python Module of the Week。请访问该网站并查看其中的目录,找一个你感兴趣的模块进行探索。从模块random开始可能是个不错的选择。

9.6 类编码风格

类名应采用驼峰命名法,即将类名中的每个单词的首字母都大写,而不使用下划线。实例名和模块名都采用小写格式,并在单词之间加上下划线。

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

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

需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的import语句,再添加一个空行,然后编写导入你自己编写的模块的import语句。

9.7 小结

在本章中,你学习了:如何编写类;如何使用属性在类中存储信息,以及如何编写方法,以让类具备所需的行为;如何编写方法__init__(),以便根据类创建包含所需属性的实例。你见识了如何修改实例的属性,包括直接修改以及通过方法进行修改。你还了解了使用继承可简化相关类的创建工作,以及将一个类的实例用作另一个类的属性可让类更简洁。

你了解到,通过将类存储在模块中,并在需要使用这些类的文件中导入它们,可让项目组织有序。你学习了Python标准库,并见识了一个使用模块random的示例。最后,你学习了编写类时应遵循的Python约定。

原网站

版权声明
本文为[axinawang]所创,转载请带上原文链接,感谢
https://blog.csdn.net/daqi1983/article/details/125539584