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

news/2024/11/29 12:43:40/

(请先看置顶博文)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 =agedef 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 = nameself.age =agedef 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 = nameself.age =agedef 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 = modelself.year = year# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = makeself.model = modelself.year = yearself.odometer_reading = 0# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = makeself.model = modelself.year = yearself.odometer_reading = 0# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse:print("You can't roll back an odometer!")

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

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

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

    def increment_odometer(self,miles):"""将里程表读数增加指定的量"""self.odometer_reading +=milesmy_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 = makeself.model = modelself.year = yearself.odometer_reading = 0# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse: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 = makeself.model = modelself.year = yearself.odometer_reading = 0# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse: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 = 70def 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 = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30  # 新定义的属性# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse: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 = 70def 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 = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30  # 新定义的属性# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse: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_sizedef 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 = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30  # 新定义的属性# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse: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_sizedef describe_battery(self):"""打印一条描述电瓶容量的消息"""print("This car has a " + str(self.battery_size) + '-kwh battery.')def get_range(self):"""打印一条信息,指出电瓶的续航里程"""if self.battery_size == 70:range = 240elif self.battery_size == 85:range = 270message = "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 = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30  # 新定义的属性# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""将里程表读数增加指定的量"""self.odometer_reading += miles# 新定义一个加油方法,并在加油结束后显示油量

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

from car import Carmy_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 = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30  # 新定义的属性# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse: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_sizedef describe_battery(self):"""打印一条描述电瓶容量的消息"""print("This car has a " + str(self.battery_size) + '-kwh battery.')def get_range(self):"""打印一条信息,指出电瓶的续航里程"""if self.battery_size == 70:range = 240elif self.battery_size == 85:range = 270message = "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 ElectricCarmy_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,ElectricCarmy_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 carmy_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 Carclass Battery():"""一次模拟电动汽车电瓶的简单尝试"""def __init__(self,battery_size=70):"""初始化电瓶的属性"""self.battery_size = battery_sizedef describe_battery(self):"""打印一条描述电瓶容量的消息"""print("This car has a " + str(self.battery_size) + "-kwh battery.")def get_range(self):"""打印一条信息,指出电瓶的续航里程"""if self.battery_size == 70:range = 240elif self.battery_size == 85:range = 270message = "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 = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30  # 新定义的属性# 类的方法def get_descriptive_name(self):"""返回整洁的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn 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 = mileageelse: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 ElectricCarmy_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 # 导入标准库里的OrderedDictfavorite_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。之所以称他们为父对象和子对象是因为:他们之间有像父子之间的联系:父对象是子对象的来源,子对象相似于父对象,但又不完全是父对象。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


http://www.ppmy.cn/news/451419.html

相关文章

软件方法(下)第8章分析之分析类图—知识篇Part07(202205更新)命名的词性和语言

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>>**** 可到此处下载《软件方法》(下)目前公开的最新pdf版本: http://www.umlchina.com/book/softmeth2.pdf 8.2.4.5 命名的词性 **类和属性应该用名词命名。**例如&…

四月嗅花 - 江湖一剑客

世间最好的默契,并非有人懂你的言外之意,而是有人懂你的欲言又止。 依然是这片土地 春天的时候生长嫩黄的油菜花 结出饱满的油菜籽 榨成菜籽 夏天的时候生长碧绿水稻 结出饱满的稻谷 加出晶莹剔透的大米 依然这片绿草地 映衬远山蜿蜒起伏 电线杆儿零落交…

依赖注入那些事儿 --看到的一篇不错的文章

1 IGame游戏公司的故事 1.1 讨论会 话说有一个叫IGame的游戏公司,正在开发一款ARPG游戏(动作&角色扮演类游戏,如魔兽世界、梦幻西游这一类的游戏)。一般这类游戏都有一个基本的功能,就是打怪(玩家攻击…

简单好听的id_王者荣耀:玩家id大盘点!看到这些好听的id立马就去建新号

在玩游戏的时候,起名字绝对是每一个玩家最能表现自己个性的时候,有些玩家偏向选择奇葩搞笑的,有些玩家喜欢诗意的名字,也有些玩家组成套名互动的。而有些超级好听的名字被其他玩家看到后,总会赶紧去建个新号把名字占住…

S7-200 PLC通信方式有哪些

更多关于西门子S7-200PLC内容请查看:西门子200系列PLC学习课程大纲(课程筹备中) S7-200 PLC通信按通信对象方式分为三种情况:A.与计算机通信;B.与其他PLC通信;C.与其他设备和仪器通信; A.S7-200 PLC与计算机通信 如下图1-1 S7-…

Vivado全版本下载分享

Vivado是由Xilinx公司开发的一款用于FPGA设计和开发的综合设计环境。它包括了高层次综合(HLS)、逻辑设计、约束管理、IP核管理、仿真、综合、实现和调试等功能,支持面向最新FPGA器件的设计。 这里分享一下Vivado的电脑安装配置推荐&#xff…

NFC Forum Type2 Tag

RC522作为一款NFC读写芯片,性价比还是很高的,因为在项目里需要采用NFC OOB配对,所以需要读取配对方模拟的NFC卡片信息 读取对象采用NRF52832,使用其NFC功能模拟type2 tag,但是读取方式和M1卡不一样,踩了不…

服务器H330阵列卡不建议做Raid5

一步一个坑,坑坑不一样,写文的时候距离事发已经很久了。 曾经遇到一个奇葩现场,服务放到服务器上部署启动后,没过多久就直接服务器都卡的不能用了,一开始怀疑是自己服务有问题,反复查看代码逻辑&#xff0c…