十三、Python第十三课——类(包含对类的相关概念的理解,这一文的附录让你彻底明白类和对象)

(请先看置顶博文)https://blog.csdn.net/GenuineMonster/article/details/104495419

目录

(请先看置顶博文)https://blog.csdn.net/GenuineMonster/article/details/104495419

 

 

 

 

一、类和对象

1、类

2、对象

二、类的创建和实例化

1、类的创建

2、类的实例化

2.1 访问属性

2.2 调用方法 

2.3 创建多个实例

3、使用类和实例

3.1 给属性指定默认值

3.2 修改属性的值

三、继承

1、子类的方法__init__()

2、在子类中定义新的属性和方法

3、重写父类的方法

4、将实例用作属性

5、模拟实物

6、导入类

6.1 导入单个类

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

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

6.4 导入整个模块

6.5 导入模块中的所有类

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

7、Python标准库的些许知识

8、写在最后

8.1 类编码风格

8.2 总结

附录 一   类的相关概念的理解 


 

 

一、类和对象

说起类的定义,不得不先提起面向对象编程。常见的编程思想有面向对象和面向过程。具体的内涵和区别可以到我的微信公众号查看,这里就不赘述了: https://mp.weixin.qq.com/s?__biz=MzkyNjAwODg1Mw==&mid=2247483701&idx=1&sn=7380c52bfbb7f73e7a71105e35fbf0fc&chksm=c23c924ff54b1b592b8be9cb2d14cf9ea5c46e5d85251f37564096919d15630911f0dc93de7b&token=177947859&lang=zh_CN#rd

在面向对象编程中,我们需要通过编写代码来表示现实世界中的事物。而现实世界中的事物千奇百怪,怎么表示呢?学过生物的都知道有: 界门纲目科属种的分类方法。这里(面向对象)我们把现实世界的事物分为各个种类,简称“类”,并基于定义的类,创建现实世界的万物。为了便于理解,我这里对类、对象、实例化给出一个非官方的理解性定义。

1、类

       类:是我们人对现实世界万事万物的抽象和提炼,把我们要研究的对象分为主要的有限的几个类。举个例子:王者荣耀手游。我们玩游戏时常见的就这么几个类(仅根据我个人的分类标准进行的分类,旨在说明类的概念,勿刚):建筑类(防御塔、泉水、商店等)、人物类(各路英雄:亚瑟、后裔等)、小兵野兽类(敌方己方野区野怪以及双方的小兵)。这么分类的标准在于“是否可被用户操纵以及是否可以移动”,当然不同的人,有不同的分类方法。那么,你会发现,面向对象指的是我们以对象为出发点,对其进行分析,根据自己的标准进行分类。当我们分类完成的时候(这其实是软件工程中的一小部分),就可以进行下一步了。我们既然可以把一些东西分为一类,那就说明他们有共同之处(同理,他们有共同之处才会被分为一类。如英雄类,均可移动和被用户操纵,都有唯一的名字,都有英雄技能,都有英雄的属性(攻击速度、攻击力、生命值等))。这些共同之处,构成了英雄类,那么英雄类的内部是否还可以细分?答案是肯定的。还可以细分为英雄属性类和英雄技能类,但是在描述时,类中还有类,这似乎有点难以描述。所以类中细分出来的英雄属性类和英雄技能类都被重命名了,叫类的属性和类的方法。其中,英雄的名字、英雄属性等都属于属性,英雄的技能和行为(击杀小兵、击杀野怪和英雄、死亡、复活、购买装备)都属于方法

类的小节:编程语言中的类其实是人对现实世界万事万物的抽象和提炼,把我们要研究的对象分为主要的、有限的几个类。类中一般包含两个成分:属性和方法(几乎所有的、面向对象的程序设计语言都是这个术语)

伪代码:以英雄类为例。

class 英雄():
      属性:
          名字
          物理攻击力
          物理防御力
          法术攻击力
          法术防御力
          攻击速度
          暴击率
          生命值
          魔法值
          ...
      方法:
          移动
          死亡
          复活
          一技能
          二技能
          三技能
          ...

2、对象

正如书中说的那样:我们在编写类时,定义一大类对象都有的通用行为。在基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要(需求)赋予每个对象独特的个性。根据类创建对象被称为实例化,我们可以指定在实例中存储什么信息,或是完成哪些操作,甚至可以编写一些类来扩展既有类的功能,让相似的类能够高效的共享代码。使用类几乎可以模拟任何东西,接下来通过一段例代码展示一下Python3.x中的类。

(对类和对象概念不清楚的同学,可以去看本博文末尾的附录一,有点大话王者荣耀的意思)

二、类的创建和实例化

1、类的创建

我们创建一个Dog类,里面将会有存储名字和年龄,并且赋予小狗蹲下和打滚的能力。

class Dog():                      # 在Python中,首字母大写的名称指的是类
                                  # 类定义的括号是空的,是因为我们要从空白创建这个类
    """一次模拟小狗的简单尝试"""    # 文档字符串,对类的功能进行描述
    def __init__(self, name, age):
        self.name = name     # 变量前都有前缀self,以self为前缀的变量都可供类中的所有方法使用
        self.age =age        # 还可以通过类的任何实例来访问这些变量。


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

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

       对代码进行一个简单的解读:Dog类中,我们定义了3个方法,分别是__init__(),sit()以及roll_over()。因为是类中的函数,所以我们在这里称其为方法。“__init__()”是一个特殊的方法,每当我们使用Dog类创建新的实例时,Python都会自动运行这个函数,类似于C++里的构造函数。在这个方法的名称中,它的开头和结尾都有两条下划线,这是一种约定,旨在避免Python默认方法和普通方法发生名称冲突。我们将方法__init__()定义成了包含三个形参:self、name、age。在这个方法的定义中,形参self必不可少,而且还必须位于其他形参的前面。为何必须在方法的定义中包含形参self呢?因为Python调用这个__init__()方法来创建Dog实例时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。我们在创建Dog实例时,Python将会调用Dog类的方法__init__()。通过实参向Dog()传递名字和年龄;self会自动传递(次次如此)。

      代码中的self.name和self.age都是可以通过实例访问的,像这样可以通过实例访问的变量称为属性。Dog类中还有两个方法,这些方法不需要额外的信息,所以它们只有一个形参self。

2、类的实例化

     类说白了就是一张构造图,里面有创建某一物体的说明,我们可以根据说明,创建出具体的东西,比如上面的Dog类。接下来我们创建一只狗:

class Dog(): # 在Python中,首字母大写的名称指的是类
             # 类定义的括号是空的,是因为我们要从空白创建这个类
    """一次模拟小狗的简单尝试"""    # 文档字符串,对类的功能进行描述
    def __init__(self, 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)  # 创建一只名为willie的6岁小狗,Python遇到这行代码,就会调用__init__()方法,将willie和6传进去创建一个表示特定小狗的实例
your_dog = Dog('lucy',3)
print("My dog's name is " + my_dog.name.title() + ". ")
print("My dog is " + str(my_dog.age) + " years old. " )

__init__()方法中并未显式的包含return语句,但Python会自动返回一个表示这条小狗的实例,并存储在my_dog中。类和实例的命名是要遵循一定的规则的:我们通常可以认为首字母大写的名称指的是类,而小写的名称指的是根据类创建的实例。

2.1 访问属性

要访问实例的属性,可以使用句点表示法,例如上述代码的my_dog.name。原理是:Python先找到实例my_dog,然后再找到与这个实例相关的属性name。上述代码的运行结果如下图:

2.2 调用方法 

根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法。如果属性和方法都指定了合适的描述性名称,即便我们从未见过代码块,也知道其功能。代码如下图所示:

class Dog(): # 在Python中,首字母大写的名称指的是类
             # 类定义的括号是空的,是因为我们要从空白创建这个类
    """一次模拟小狗的简单尝试"""
    def __init__(self, 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)
your_dog = Dog('lucy',3)
print("My dog's name is " + my_dog.name.title() + ". ")
print("My dog is " + str(my_dog.age) + " years old. " )
my_dog.sit()
my_dog.roll_over()

2.3 创建多个实例

刚才说到,类是一张构造图。一旦有了类,你想创建多少个实例,都可以。你可以按照需求创建任意数量的实例。例代码:

class Dog(): # 在Python中,首字母大写的名称指的是类
             # 类定义的括号是空的,是因为我们要从空白创建这个类
    """一次模拟小狗的简单尝试"""
    def __init__(self, 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)
your_dog = Dog('lucy',3)
print("My dog's name is " + my_dog.name.title() + ". ")
print("My dog is " + str(my_dog.age) + " years old. " )
my_dog.sit()
my_dog.roll_over()

print("\nYour dog's name is " + your_dog.name.title() + ". ")
print("Your dog is " + str(your_dog.age) + " years old. " )
your_dog.sit()

3、使用类和实例

       学了类之后,我们可以使用类来模拟现实世界的很多情景。类编写好了之后,更多的时间将会花在使用根据类创建的实例上。需要执行的一个重要任务是修改实例的属性,可以直接修改,也可以编写方法以特定的方式修改。接下来创建汽车类,来展示属性修改的不同方法。

例代码:

class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make    # 3个属性,意味着创建新的car时,需要指定制造商、型号和生产年份
        self.model = model
        self.year = year
      

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

# 实例化
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())

3.1 给属性指定默认值

类中的每个属性都必须有初始值,0和空字符串都行。在有些情况下,如设置默认值时,在方法__init__()内指定这种初始值是可行的;如果已对某个属性指定了初始值,就无需包含为它提供初始值的形参。我们为上述代码增加一个名为odometer_reading的属性,其初始值总是为0。我们还添加一个名为read_odometer()的方法,用于读取汽车的里程表:

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_reading(self):
        """打印一条指出汽车里程的信息"""
        print("This car has " + str(self.odometer_reading) + " miles on it.")

# 实例化
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer_reading()

3.2 修改属性的值

可以以三种不同的方式修改属性的值:A、直接通过实例进行修改。B、通过方法进行设置。C、通过方法进行递增(增加特定的值)

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

使用句点表示法来直接访问并设置汽车的属性odometer_reading。下列代码的第一行,让Python在实例my_new_car中找到属性odometer_reading,并将该属性的值设置为23。

my_new_car.odometer_reading = 23
my_new_car.read_odometer_reading()

B、通过方法修改属性的值

如果有替你更新属性的方法,将省事的多。这样,我们就无需直接访问属性。可以将值传递给一个方法,由它在内部对属性的内容进行更新,我们为代码增添一个名为update_odometer()方法。对代码所做的唯一修改是添加了方法update_odometer()。这个方法接受一个里程值,并将其存储到self.odometer_reading中。

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_reading(self):
        """打印一条指出汽车里程的信息"""
        print("This car has " + str(self.odometer_reading) + " miles on it.")

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




# 实例化
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())

# 新增加的代码
my_new_car.update_odometer(23)
my_new_car.read_odometer_reading()

可以对方法update_odometer()进行扩展,使其在修改里程表读数时做些额外的工作。比如禁止任何人回调里程表的读数:

    def update_odometer(self,mileage):
        """将里程表读数设置为指定的值
        禁止将里程表读数往回调"""
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")

增添了这段代码后,update_odometer()在修改属性前检查指定的读数是否合理。合理就修改,不合理将会有警告信息。

C、通过方法对属性的值进行递增

假设我们买了一辆二手车,从购买到登记期间增加了100英里的里程,通过下面新定义的方法,相应的增加里程表读数:

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


my_used_car = Car('subaru','outback',2013)
print(my_used_car.get_descriptive_name())

my_used_car.update_odometer(23500)
my_used_car.read_odometer_reading()

my_used_car.increment_odometer(100)
my_used_car.read_odometer_reading()

当然可以轻松的修改以上的方法,防止增量为负值。

三、继承

编写类时,并非总是要从空白开始。如果你要编写的类是另一个现成类的特殊版本,此时就可以使用继承。继承的意思就是我们继承文化、继承遗产中继承的意思,无需多说。当一个类继承另一个类时,它将自动获得另一个类的所有属性和方法原有的类称为父类,而新类称为子类。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法

1、子类的方法__init__()

创建子类的实例时,Python首先需要完成的任务是给父类的所有属性赋值。为此,子类的方法__init__()需要父类施以援手。例如,下面我们将会模拟电动汽车。电动汽车是一种特殊的汽车,因此我们可以基于前面创建的Car类来创建ElectricCar,这样我们就只需为电动车特有的属性和行为编写代码就ok了。例代码如下:



# 父类
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_reading(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("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', 2016)
print(my_tesla.get_descriptive_name())

对上述代码的分析:映入眼帘的首先是Car类的代码(父类)。

A、创建ElectricCar(子类)时,Car(父类)必须包含在当前文件中,且位于子类的前面。

B、定义子类时,必须在括号内指定父类的名称。另外,还得在子类的__init__()方法中接受创建Car实例所需的信息。

C、super()是一个特殊的函数,帮助Python将父类和子类关联起来。代码中的这一行代码“super().__init__(make,model,year)”让Python调用ElectricCar的父类的方法__init__(),让ElectricCar实例包含父类的所有属性。父类也称为超类,名称super因此得名。

D、一定要记得在定义子类时,要在子类的__init__()方法中初始化父类的属性

2、在子类中定义新的属性和方法

子类中可添加新的属性和方法,下面我们添加一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法。我们将存储电瓶的容量,并编写一个打印电瓶描述的方法,全部代码如下:


# 父类
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_reading(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("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)
        self.battery_size = 70

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



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

在上述代码中,我们在子类中添加了新属性self.battery_size,并设置其初始值70。根据ElectricCar类创建的所有实例都将会包含这个属性,但所有的Car实例都不包含它。如果一个属性或方法是任何汽车都有的,而不是电动汽车特有的,就应该将其加入到Car类而不是ElectricCar类中。如此一来,使用Car类的人将获得相应的功能,而ElectricCar类只包含处理电动汽车特有的属性和行为的代码。

3、重写父类的方法

对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与重要的父类方法同名。这样,Python编译器将不会考虑这个父类方法,而只关注你在子类中定义的相应的方法。下面我们在Car类中增添油箱属性和对应的方法,并在子类ElectricCar中重写Car类的对应的方法,验证“Python只关注你在子类中定义的相应的方法”这一观点。

# 父类
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
        self.gas = 30  # 新定义的属性

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

    def read_odometer_reading(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("You can't roll back an odometer!")

    def increment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading +=miles
     # 新定义一个加油方法,并在加油结束后显示油量
    def fill_gas_tank(self):
        print("gas' num is : " + str(self.gas))
# 子类
class ElectricCar(Car):
    """电动汽车的独特之处"""

    def __init__(self,make,model,year):
        """初始化父类的属性
           在初始化电动汽车特有的属性
        """      # 一定要记得初始化父类的属性
        super().__init__(make,model,year)
        self.battery_size = 70

    def describe_battery(self):
        """打印一条描述电瓶容量的消息"""
        print("This car has a " + str(self.battery_size) + "-kwh 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.describe_battery()
my_tesla.fill_gas_tank()

以上代码是在子类中重写父类方法的代码,对应的输出结果如下所示:

如果不在子类中重写父类的代码,那么输出结果将会是这样的:

由此看来,验证的观点是正确的,以后在修改父类方法时,就按照这个方法。所以在使用继承时,可以让子类保留父类那里继承而来的精华,并提出不需要的糟粕。

4、将实例用作属性

使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来,你可以将大型类拆分成多个协同工作的小类。举个例子,我们不断给ElectricCar类添加细节时,可能会发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,我们可将这些属性和方法提取出来,放到另一个名为Battery的类中,并将一个Battery实例用作ElectricCar类的一个属性(这是将实例用作属性的解释)例代码如下:

# 父类
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
        self.gas = 30  # 新定义的属性

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

    def read_odometer_reading(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("You can't roll back an odometer!")

    def increment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading +=miles
     # 新定义一个加油方法,并在加油结束后显示油量
    def fill_gas_tank(self):
        print("gas' num is : " + str(self.gas))


# 电池类
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.')


# 电动车子类
class ElectricCar(Car):
    """电动汽车的独特之处"""

    def __init__(self,make,model,year):
        """初始化父类的属性
           在初始化电动汽车特有的属性
        """      # 一定要记得初始化父类的属性
        super().__init__(make,model,year)
        self.battery = Battery()

    def describe_battery(self):
        """打印一条描述电瓶容量的消息"""
        print("This car has a " + str(self.battery_size) + "-kwh 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()

上面的代码增加了电池类Battery,它没有继承任何类,方法describ_battery()也被移到了这个类中。除此之外,我们还在ElectricCar中增添了电池类的实例化代码,将电池类的实例化用作属性,并存储在self.battery中。由于没有指定尺寸,所以默认为70。每当方法__init__()被调用时,都将执行该操作;因此现在每个ElectricCar实例都包含一个自动创建的Battery实例。此时,如果要描述电瓶时,则需要使用句点表示法,如“my_tesla.battery.describe_battery()"。这行代码让Python在实例my_tesla中查找属性battery,并对存储在该属性中的Battery实例调用方法describe_battery()。结果输出如下:

这个结果和上面的一样,并且做了很多工作,有点费力不讨好的意思。但是,这样做是有很大优点的:我们现在想多详细的描述电瓶都可以,且不会导致ElectricCar类混乱不堪。下面再给Battery类增添一个方法,使汽车根据电瓶容量报告汽车的续航里程:

# 父类
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
        self.gas = 30  # 新定义的属性

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

    def read_odometer_reading(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("You can't roll back an odometer!")

    def increment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading +=miles
     # 新定义一个加油方法,并在加油结束后显示油量
    def fill_gas_tank(self):
        print("gas' num is : " + str(self.gas))


# 电池类
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_size == 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 describe_battery(self):
        """打印一条描述电瓶容量的消息"""
        print("This car has a " + str(self.battery_size) + "-kwh 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()

新增的get_range()做了一些简单的分析:如果电瓶的容量为70kwh,它就将续航里程设置为240英里;若是85kwh,那就将续航里程设置为270英里,然后报告这个值。输出结果如下:

5、模拟实物

      如果我们需要模拟较为复杂的物件时,需要解决很多问题。例如,续航里程是电瓶的属性还是汽车的属性呢?如果我们只需描述一辆汽车,那么将方法get_range()放在Battery类中也许是合适的;但如果要描述一家汽车制造商的整个产品线,也许将方法get_range()移到ElectricCar类中。在这种情况下,get_range()将根据电瓶容量和汽车型号报告续航里程。这已经涉及到类的设计(软件工程)了,是从较高的逻辑层考虑问题。前面的笔记中也说过,只要代码能完成所需要的功能就可以了,慢慢的优化,直至写出高效、准确的代码。

6、导入类

随着你不断地给类添加功能,文件可能变得很长,即便你妥善地使用了继承,也是会出现这个问题。为了让代码总体看起来更整洁,Python允许我们将类存储在模块中,然后在主程序中导入所需的模块。(代码模块化)

6.1 导入单个类

下面我们将创建一个只包含Car类的模块。单独创建一个py文件,命名为Car.py,把如下代码存进去:(开头第一行是一个模块级文档字符串,对该模块的内容做了简要的描述。我们应该为自己创建的每个模块都编写文档字符串)

"""一个可用于表示汽车的类"""
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
        self.gas = 30  # 新定义的属性

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

    def read_odometer_reading(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("You can't roll back an odometer!")

    def increment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles
     # 新定义一个加油方法,并在加油结束后显示油量

随后,我们创建一个新的py文件,名为my_car.py,里面写入如下代码:

from car import Car

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

my_new_car.odometer_reading = 23
my_new_car.read_odometer_reading()

导入类是一种有效编程,通过导入类这一方式。主程序变得简洁易读,而自身也可以专注于主程序的高级逻辑了。

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

虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类。例代码将会展示把Car类、Battery类、ElectricCar类三个类放入car.py文件中,并新建一个名为:my_electric_car.py的文件。里面的代码依次为:

"""一组用于表示燃油汽车和电动汽车的类"""
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
        self.gas = 30  # 新定义的属性

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

    def read_odometer_reading(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("You can't roll back an odometer!")

    def increment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading +=miles
     # 新定义一个加油方法,并在加油结束后显示油量


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_size == 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()
from car import ElectricCar

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

输出为:

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

可根据需要在程序文件中导入任意数量的类。如果我们要在同一个程序中创建普通汽车和电动汽车,就需要将Car和ElectricCar类都导入,代码为:

from car import Car,ElectricCar

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

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

输出为:

6.4 导入整个模块

可以直接在程序开头导入整个模块,再使用句点表示法访问需要的类。这种导入方法很简单,代码也易于阅读。

import car

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

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

输出为:

6.5 导入模块中的所有类

要导入模块中的每个类,使用的语法是:

from module_name importr *

这种导入模块的方法是不推荐的,有两个原因:

A、首先,如果只要看一下文件开头的import语句,就能清楚地知道程序使用了哪些类,将大有脾益;但这种导入方式没有明确地指出你使用了模块中的哪些类。

B、这种导入方式还可能引发名称方面的困惑。如果你不小心导入了一个与程序文件其他东西同名的类,将引发难以诊断的错误。(不推荐但依旧介绍的原因是:有可能在阅读代码的过程中遇到这类的用法)

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

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

有时候需要将类分散到多个模块中,以免模块太大,或在同一个模块中存储不相关的类。将类存储在多个模块中时,你可能会发现一个模块中的类依赖另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类。例如,将上述写好的几个类进行如下组织:

将Car类存储在一个模块中,并将ElectricCar和Battery类存储在另一个模块中,我们将后者的模块命名为“electric_car.py”,并将对应的代码存储在这个模块中,electric_car.py文件中的代码如下:(ElectricCar类需要访问其父类Car,所以在代码开头,直接将Car了类导入到该模块中)

from car import Car

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_size == 85:
            range = 270

        message = "This cae 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()

car.py中的代码如下所示:

"""一组用于表示燃油汽车和电动汽车的类"""
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
        self.gas = 30  # 新定义的属性

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

    def read_odometer_reading(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("You can't roll back an odometer!")

    def increment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading +=miles
     # 新定义一个加油方法,并在加油结束后显示油量

my_cars.py中的代码如下所示:(现在可以分别从每个模块中导入类,以根据需要创建任何类型的汽车了)

from car import Car
from electric_car import ElectricCar

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

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

输出为:

7、Python标准库的些许知识

我们学会了如何导入模块,那么接下来我们尝试一下:(导入Python标准库,记录被调查者喜欢的编程语言)

from collections import OrderedDict # 导入标准库里的OrderedDict

favorite_languages = OrderedDict() # 使用OrderedDict()创建一个实例(空的有序字典),并存储在favorite_languages中

favorite_languages['jen'] = 'python' # 将四个人的名字及其喜欢的编程语言写到favorite_languages字典中
favorite_languages['sarch'] = 'c'
favorite_languages['edward'] = 'ruby'
favorite_languages['phil'] = 'python'
# 利用循环将字典中存储的信息按照顺序输出
# items()以及title()这两个函数在之前的笔记中有
for name,language in favorite_languages.items():
    print(name.title() + "'s favorite language is " + language.title() + ". ")

8、写在最后

8.1 类编码风格

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

B、对于每一个类,都应在类定义后面紧跟着一个文档字符串。这个文档字符串应简要的描述类的功能和用途,并遵循编写函数的文档字符串时采用的格式约定。

C、可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分割类。

D、需要同时导入标准库中的模块和自编写库的模块时,先导入标准库的,后倒入自编写库的。可以让人知道这些模块来源于哪。

8.2 总结

我们在进行大型项目的开发时,一开始要让代码结构尽可能简单:现在一个文件中完成所有的工作,确定一切都能正确运行后,在将类移到独立的模块中。先成功,后成“精”。当然,如果是小组合作,完成一个大的项目,那就按照软件工程那一套来,先设计,后实现。设计类是怎么设计的,就怎么实现。

附录 一   类的相关概念的理解 

因为有的书籍把类实例化后的产物也叫对象 ,所以,我在这里提出父对象和子对象的概念,便于大家理解,实际上并无这一术语,望周知。特别注意红色字。

对象:在我看来,对象是集合。并且对象实际上有两种,一种是父对象,一种是子对象。

父对象:人类实际生活中的万事万物。对父对象进行研究可产生某一类。

子对象:基于对应的类,进行实例化操作,产生了子对象。

接下来的内容主要以游戏开发过程为背景,英雄类为例子,为大家故事式的解释什么是类?类是怎么产生的?类和对象的关系是什么?类的实例化是什么?

       假设,观看此博文的各位已经被我以高价签约,共同开发《王者荣耀pro》5v5大型手机游戏。我们全部从零开始,没有可利用的代码,一切代码全凭自己的双手敲出。我是老板兼项目经理,召开了一次大会,提出了一个以“古今中外的神话人物、历史名人等”为主要角色的游戏开发项目,让公司的营业额再上层楼,并你们施以金钱诱惑。大家因为金钱的鼓舞,热血沸腾。小明率先提出:“我觉得游戏中应该有这么一类暂且称它为A类,就拿中国历史名人来说,如孙尚香、白起等,他们有如下特点:能移动(反映到游戏中就是得受用户控制);同时他们各自在实际历史中有自己的本事(反映到游戏中是得有一些技能);最后还得有一些属性(反应到游戏中是得有一些具体的参数)......”我拍手叫好,并任命小明为类A开发组组长,开发对应的代码。大家伙一看有官可取,纷纷提出了游戏中还应该存在的类B、C、D、E、F、G......与小明的经历相同,大家都升官发财了,这个游戏的开发就此拉开了帷幕。(此段中,小明提出A类所依据的中国历史人物即是上文提到的父对象,也即现实生活中的万事万物;“对应到游戏中的操作”即为游戏的设计想法)

       期间,我们抽空又开了一次会,对正在开发的类的名称、属性名、方法名等进行了讨论。其他的类自行命名,合理即可。小明负责的类被我命名为“英雄类”。英雄类根据类的定义又分为类的属性和类的方法,具体内容如下图所示。小明带领团队加班加点,终于率先完成了“英雄类”的开发。(把父对象与游戏设计想法进行融合,随后再提炼、抽象、丰富即可得到英雄类。在软件工程中,这叫类的设计。如果像小明团队这样,这就叫做类的实现。父对象是类的前驱,类是父对象的后驱。

       因为小明的团队效率高,我又单独找到他,委以重任:“光有了英雄类可不行,还得有具体的英雄。反正类已经有了,简单几行代码,对英雄类实例化一下,构造几个用来内测的英雄吧!到时候游戏上线了,再提拔你一下。”小明答应了,并根据我国的神话传说及历史典故,在基于英雄类的基础上,构造了3-4个英雄:孙尚香、白起、后裔等。(此时,小明团队根据“英雄类”构造出来的英雄如孙尚香、白起、后裔等均是对象(我看的那本书这样称呼),所有的对象的总和即是我为了大家便于理解而称呼的“子对象”(也有的书称呼为实例)。由类创建子对象的过被称为类的实例化)

       最终,在我们团队的不懈努力下,游戏成功上线,公司盈利额蒸蒸日上。日后,我们只需要使用英雄类的实例化代码,即可构造出多个不同的英雄,以及皮肤,这简直是一劳永逸啊!(面向对象的编程思想提高了代码的复用率,省时省力——面向对象的部分优点)

两种对象的对比(父对象、子对象):

       项目需求中提到的游戏背景、角色来源、游戏模式等可能就已经对父对象进行了限制,无论怎么限制,父对象均是实际生活中的万事万物(父对象中的孙尚香指的就是实际历史中的孙尚香),但经过游戏设计理念的洗礼,父对象中的实际历史人物很可能和子对象中的游戏角色在性别、着装等方面完全不一样(想必大家也深有体会)。但也有相同之处,比如都可以走、跳、释放技能等。在规模上,因为游戏金钱成本或时间原因,父对象的数量规模很有可能比子对象的规模大的多。就如历史上的英雄豪杰千千万,游戏里的角色总共加起来也没有1000。之所以称他们为父对象和子对象是因为:他们之间有像父子之间的联系:父对象是子对象的来源,子对象相似于父对象,但又不完全是父对象。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liO_Oil

打赏我,开启隐藏模式。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值