一、摘要
面向对象编程 是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建对象 时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景,其逼真程度达到了令你惊讶的地步。根据类来创建对象被称为实例化 ,这让你能够使用类的实例。
二、创建和使用类
使用类几乎可以模拟任何东西。
下面来编写一个表示小狗的简单类Dog ——它表示的不是特定的小狗,而是任何小狗。对于大多数宠物狗,我们都知道些什么呢?它们都有名字和年龄;我们还知道,大多数小狗还会蹲下和打滚。由于大多数小狗都具备上述两项信息(名字和年龄)和两种行为(蹲下和打滚),我们的Dog 类将包含它们。这个类让Python知道如何创建表示小狗的对象。编写这个类后,我们将使用它来创建表示特定小狗的实例。
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!")
__init__() 是一个特殊的方法,每当你根据Dog 类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突
我们将方法__init__() 定义成了包含三个形参:self 、name 和age 。在这个方法的定义中,形参self 必不可少,还必须位于其他形参的前面。为何必须在方法定义中包含形参self 呢?因为Python调用这个__init__() 方法来创建Dog 实例时,将自动传入实参self 。每个与类相关联的方法调用都自动传递实参self ,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。我们创建Dog 实例时,Python将调用Dog 类的方法__init__()
我们将通过实参向Dog() 传递名字和年龄;self 会自动传递,因此我们不需要传递它。每当我们根据Dog 类创建实例时,都只需给最后两个形参(name 和age )提供值
处定义的两个变量都有前缀self 。以self 为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。self.name = name 获取存储在形参name 中的值,并将其存储到变量name 中,然后该变量被关联到当前创建的实例。self.age = age 的作用与此类似。像这样可通过实例访问的变量称为属性
Dog 类还定义了另外两个方法:sit() 和roll_over(),由于这些方法不需要额外的信息,如名字或年龄,因此它们只有一个形参self 。我们后面将创建的实例能够访问这些方法,换句话说,它们都会蹲下和打滚。当前,sit() 和roll_over() 所做的有限,它们只是打印一条消息,指出小狗正蹲下或打滚。但可以扩展这些方法以模拟实际情况:如果这个类包含在一个计算机游戏中,这些方法将包含创建小狗蹲下和打滚动画效果的代码。如果这个类是用于控制机器狗的,这些方法将引导机器狗做出蹲下和打滚的动作
根据类创建实例:
class Dog(): --snip-- my_dog = Dog('willie', 6) print("My dog's name is " + my_dog.name.title() + ".") print("My dog is " + str(my_dog.age) + " years old.")
我们让Python创建一条名字为'willie' 、年龄为6 的小狗。遇到这行代码时,Python使用实参'willie' 和6 调用Dog 类中的方法__init__() 。方法__init__() 创建一个表示特定小狗的示例,并使用我们提供的值来设置属性name 和age 。方法__init__() 并未显式地包含return 语句,但Python自动返回一个表示这条小狗的实例。我们将这个实例存储在变量my_dog 中。在这里,命名约定很有用:我们通常可以认为首字母大写的名称(如Dog )指的是类,而小写的名称(如my_dog )指的是根据类创建的实例
访问属性:
要访问实例的属性,可使用句点表示法,如下代码来访问my_dog 的属性name 的值:
my_dog.name
句点表示法在Python中很常用,这种语法演示了Python如何获悉属性的值。在这里,Python先找到实例my_dog ,再查找与这个实例相关联的属性name 。在Dog 类中引用这个属性时,使用的是self.name 。我们使用同样的方法来获取属性age 的值。在前面的第1条print 语句中,my_dog.name.title() 将my_dog 的属性name 的值'willie' 改为首字母大写的;在第2条print 语句中,str(my_dog.age) 将my_dog 的属性age 的值6 转换为字符串。输出是有关my_dog 的摘要:
My dog's name is Willie. My dog is 6 years old.
调用方法:
根据Dog 类创建实例后,就可以使用句点表示法来调用Dog 类中定义的任何方法
class Dog(): --snip-- my_dog = Dog('willie', 6) my_dog.sit() my_dog.roll_over()
要调用方法,可指定实例的名称(这里是my_dog )和要调用的方法,并用句点分隔它们。遇到代码my_dog.sit() 时,Python在类Dog 中查找方法sit() 并运行其代码。Python以同样的方式解读代码my_dog.roll_over()
Willie is now sitting. Willie rolled over!
在命名上,如果给属性和方法指定了合适的描述性名称,如name 、age 、sit() 和roll_over() ,即便是从未见过的代码块,我们也能够轻松地推断出它是做什么的
创建多个实例:
class Dog(): --snip-- 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() print("\nYour dog's name is " + your_dog.name.title() + ".") print("Your dog is " + str(your_dog.age) + " years old.") your_dog.sit()
就算我们给第二条小狗指定同样的名字和年龄,Python依然会根据Dog 类创建另一个实例。你可按需求根据一个类创建任意数量的实例,条件是将每个实例都存储在不同的变量中,或占用列表或字典的不同位置。
三、使用类和实例
你可以使用类来模拟现实世界中的很多情景。类编写好后,你的大部分时间都将花在使用根据类创建的实例上。你需要执行的一个重要任务是修改实例的属性。你可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。
class Car(): """一次模拟汽车的简单尝试""" def __init__(self, make, model, year): """初始化描述汽车的属性""" self.make = make 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())
定义了方法__init__() 。与前面的Dog 类中一样,这个方法的第一个形参为self ;我们还在这个方法中包含了另外三个形参:make 、model 和year 。方法__init__() 接受这些形参的值,并将它们存储在根据这个类创建的实例的属性中。创建新的Car 实例时,我们需要指定其制造商、型号和生产年份
定义了一个名为get_descriptive_name() 的方法,它使用属性year 、make 和model 创建一个对汽车进行描述的字符串,让我们无需分别打印每个属性的值。为在这个方法中访问属性的值,我们使用了self.make 、self.model 和self.year 。
我们根据Car 类创建了一个实例,并将其存储到变量my_new_car中。接下来,我们调用方法get_descriptive_name() ,指出我们拥有的是一辆什么样的汽车:
2016 Audi A4
给属性指定默认值:
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): --snip-- def read_odometer(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()
当Python调用方法__init__() 来创建新实例时,将像前一个示例一样以属性的方式存储制造商、型号和生产年份。接下来,Python将创建一个名为odometer_reading 的属性,并将其初始值设置为0。
我们还定义了一个名为read_odometer() 的方法,它让你能够轻松地获悉汽车的里程。一开始汽车的里程为0:
2016 Audi A4
This car has 0 miles on it.
修改属性的值:
可以以三种不同的方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值)
直接修改属性的值:
class Car(): --snip-- 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()
我们使用句点表示法来直接访问并设置汽车的属性odometer_reading 。这行代码让Python在实例my_new_car 中找到属性odometer_reading ,并将该属性的值设置为23:
2016 Audi A4
This car has 23 miles on it.
通过方法修改属性的值:
如果有替你更新属性的方法,将大有裨益。这样,你就无需直接访问属性,而可将值传递给一个方法,由它在内部进行更新
class Car(): --snip-- 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()
添加了方法update_odometer() 。这个方法接受一个里程值,并将其存储到self.odometer_reading 中。
我们调用了update_odometer() ,并向它提供了实参23(该实参对应于方法定义中的形参mileage)。它将里程表读数设置为23;而方法read_odometer() 打印该读数:
2016 Audi A4
This car has 23 miles on it.
再看一个例子:
class Car(): --snip-- 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() 在修改属性前检查指定的读数是否合理。如果新指定的里程(mileage )大于或等于原来的里程(self.odometer_reading ),就将里程表读数改为新指定的里程
否则就发出警告,指出不能将里程表往回拨
通过方法对属性的值进行递增:
class Car(): --snip-- def update_odometer(self, mileage): --snip-- 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() my_used_car.increment_odometer(100) my_used_car.read_odometer()
新增的方法increment_odometer() 接受一个单位为英里的数字,并将其加入到self.odometer_reading 中。
我们创建了一辆二手车my_used_car,调用方法update_odometer() 并传入23500 ,将这辆二手车的里程表读数设置为23 500。
我们调用increment_odometer()并传入100 ,以增加从购买到登记期间行驶的100英里:
2013 Subaru Outback This car has 23500 miles on it. This car has 23600 miles on it.
你可以轻松地修改这个方法,以禁止增量为负值,从而防止有人利用它来回拨里程表。
注意:你可以使用类似于上面的方法来控制用户修改属性值(如里程表读数)的方式,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值。要确保安全,除了进行类似于前面的基本检查外,还需特别注意细节。
四、继承
编写类时,并非总是要从空白开始。如果你要编写的类是另一个现成类的特殊版本,可使用继承 。一个类继承 另一个类时,它将自动获得另一个类的所有属性和方法;原有的类称为父类 ,而新类称为子类 。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。
子类的方法__init__():
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("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 类的代码。创建子类时,父类必须包含在当前文件中,且位于子类前面
定义了子类ElectricCar 。定义子类时,必须在括号内指定父类的名称。方法__init__() 接受创建Car 实例所需的信息
super() 是一个特殊函数,帮助Python将父类和子类关联起来。这行代码让Python调用ElectricCar 的父类的方法__init__() ,让ElectricCar 实例包含父类的所有属性。父类也称为超类 (superclass),名称super因此而得名
为测试继承是否能够正确地发挥作用,我们尝试创建一辆电动汽车ElectricCar 类的一个实例,但提供的信息与创建普通汽车时相同,并将其存储在变量my_tesla 中。这行代码调用ElectricCar 类中定义的方法__init__() ,后者让Python调用父类Car 中定义的方法__init__() 。我们提供了实参'tesla' 、'models' 和2016
除方法__init__() 外,电动汽车没有其他特有的属性和方法。当前,我们只想确认电动汽车具备普通汽车的行为:
2016 Tesla Model S
给子类定义属性和方法:
class Car(): --snip-- class ElectricCar(Car): """Represent aspects of a car, specific to electric vehicles.""" 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 实例都不包含它。
添加了一个名为describe_battery() 的方法,它打印有关电瓶的信息。我们调用这个方法时,将看到一条电动汽车特有的描述:
2016 Tesla Model S
This car has a 70-kWh battery.
对于ElectricCar 类的特殊化程度没有任何限制。模拟电动汽车时,你可以根据所需的准确程度添加任意数量的属性和方法。如果一个属性或方法是任何汽车都有的,而不是电动汽车特有的,就应将其加入到Car 类而不是ElectricCar 类中。这样,使用Car 类的人将获得相应的功能,而ElectricCar 类只包含处理电动汽车特有属性和行为的代码
重写父类的方法:
对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与要重写的父类方法同名。这样,Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。假设Car 类有一个名为fill_gas_tank() 的方法,它对全电动汽车来说毫无意义,因此你可能想重写它。下面演示了一种重写方式:
def ElectricCar(Car): --snip-- def fill_gas_tank(): """电动汽车没有油箱""" print("This car doesn't need a gas tank!")
现在,如果有人对电动汽车调用方法fill_gas_tank() ,Python将忽略Car 类中的方法fill_gas_tank() ,转而运行上述代码。使用继承时,可让子类保留从父类那里继承而来的精华,并剔除不需要的糟粕。
将实例用作属性:
使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。你可以将大型类拆分成多个协同工作的小类。
例如,不断给ElectricCar 类添加细节时,我们可能会发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,我们可将这些属性和方法提取出来,放到另一个名为Battery 的类中,并将一个Battery 实例用作ElectricCar 类的一个属性:
class Car(): --snip-- 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() my_tesla = ElectricCar('tesla', 'model s', 2016) print(my_tesla.get_descriptive_name()) my_tesla.battery.describe_battery()
定义了一个名为Battery 的新类,它没有继承任何类,__init__() 除self 外,还有另一个形参battery_size 。这个形参是可选的:如果没有给它提供值,电瓶容量将被设置为70。方法describe_battery() 也移到了这个类中
在ElectricCar 类中,我们添加了一个名为self.battery 的属性,这行代码让Python创建一个新的Battery 实例(由于没有指定尺寸,因此为默认值70 ),并将该实例存储在属性self.battery 中。每当方法__init__() 被调用时,都将执行该操作;因此现在每个ElectricCar 实例都包含一个自动创建的Battery 实例。
我们创建一辆电动汽车,并将其存储在变量my_tesla 中。要描述电瓶时,需要使用电动汽车的属性battery :
my_tesla.battery.describe_battery()
这行代码让Python在实例my_tesla 中查找属性battery ,并对存储在该属性中的Battery 实例调用方法describe_battery()
执行结果为:
2016 Tesla Model S This car has a 70-kWh battery.
这看似做了很多额外的工作,但现在我们想多详细地描述电瓶都可以,且不会导致ElectricCar 类混乱不堪。下面再给Battery 类添加一个方法,它根据电瓶容量报告汽车的续航里程:
class Car(): --snip-- class Battery(): --snip-- 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): --snip-- 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英里,然后报告这个值。为使用这个方法,我们也通过汽车的属性battery 来调用它
输出指出了汽车的续航里程(这取决于电瓶的容量):
执行结果为:
2016 Tesla Model S This car has a 70-kWh battery. This car can go approximately 240 miles on a full charge.
模拟较复杂的物件(如电动汽车)时,需要解决一些有趣的问题。续航里程是电瓶的属性还是汽车的属性呢?如果我们只需描述一辆汽车,那么将方法get_range() 放在Battery 类中也许是合适的;但如果要描述一家汽车制造商的整个产品线,也许应该将方法get_range() 移到ElectricCar 类中。在这种情况下,get_range() 依然根据电瓶容量来确定续航里程,但报告的是一款汽车的续航里程。我们也可以这样做:将方法get_range() 还留在Battery 类中,但向它传递一个参数,如car_model ;在这种情况下,方法get_range() 将根据电瓶容量和汽车型号报告续航里程。这让你进入了程序员的另一个境界:解决上述问题时,你从较高的逻辑层面(而不是语法层面)考虑;你考虑的不是Python,而是如何使用代码来表示实物。到达这种境界后,你经常会发现,现实世界的建模方法并没有对错之分。有些方法的效率更高,但要找出效率最高的表示法,需要经过一定的实践。只要代码像你希望的那样运行,就说明你做得很好!即便你发现自己不得不多次尝试使用不同的方法来重写类,也不必气馁;要编写出高效、准确的代码,都得经过这样的过程。
五、导入类
随着你不断地给类添加功能,文件可能变得很长,即便你妥善地使用了继承亦如此。为遵循Python的总体理念,应让文件尽可能整洁。为在这方面提供帮助,Python允许你将类存储在模块中,然后在主程序中导入所需的模块。
创建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("You can't roll back an odometer!") def increment_odometer(self, miles): """将里程表读数增加指定的量""" self.odometer_reading += miles
文件的开头,包含了一个模块级文档字符串,对该模块的内容做了简要的描述。你应为自己创建的每个模块都编写文档字符串。
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()
import 语句让Python打开模块car ,并导入其中的Car 类。这样我们就可以使用Car 类了,就像它是在这个文件中定义的一样。输出与我们在前面看到的一样:
执行结果为:
2016 Audi A4 This car has 23 miles on it.
虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类。类Battery 和ElectricCar 都可帮助模拟汽车,因此下面将它们都加入模块car.py中:
"""一组用于表示燃油汽车和电动汽车的类""" class Car(): --snip-- class Battery(): """一次模拟电动汽车电瓶的简单尝试""" def __init__(self, battery_size=60): """初始化电瓶的属性""" 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()
我们就可以import ElectricCar类了
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()
执行结果为:
2016 Tesla Model S This car has a 70-kWh battery. This car can go approximately 240 miles on a full charge.
可根据需要在程序文件中导入任意数量的类。如果我们要在同一个程序中创建普通汽车和电动汽车,就需要将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())
执行结果为:
2016 Volkswagen Beetle 2016 Tesla Roadster
六、导入整个模块
你还可以导入整个模块,再使用句点表示法访问需要的类。这种导入方法很简单,代码也易于阅读。由于创建类实例的代码都包含模块名,因此不会与当前文件使用的任何名称发生冲突。
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())
导入了整个car 模块。接下来,使用语法 module_name.class_name 访问需要的类。
导入模块中的所有类:
from module_name import *
不推荐使用这种导入方式,其原因有二。首先,如果只要看一下文件开头的import 语句,就能清楚地知道程序使用了哪些类,将大有裨益;但这种导入方式没有明确地指出你使用了模块中的哪些类。这种导入方式还可能引发名称方面的困惑。如果你不小心导入了一个与程序文件中其他东西同名的类,将引发难以诊断的错误。这里之所以介绍这种导入方式,是因为虽然不推荐使用这种方式,但你可能会在别人编写的代码中见到它。需要从一个模块中导入很多类时,最好导入整个模块,并使用 module_name.class_name 语法来访问类。这样做时,虽然文件开头并没有列出用到的所有类,但你清楚地知道在程序的哪些地方使用了导入的模块;你还避免了导入模块中的每个类可能引发的名称冲突。
在一个模块中导入另一个模块:
有时候,需要将类分散到多个模块中,以免模块太大,或在同一个模块中存储不相关的类。将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类。例如,下面将Car 类存储在一个模块中,并将ElectricCar 和Battery 类存储在另一个模块中。我们将第二个模块命名为electric_car.py
car.py
"""A class that can be used to represent a car.""" class Car(): """A simple attempt to represent a car.""" def __init__(self, manufacturer, model, year): """Initialize attributes to describe a car.""" self.manufacturer = manufacturer self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): """Return a neatly formatted descriptive name.""" long_name = str(self.year) + ' ' + self.manufacturer + ' ' + self.model return long_name.title() def read_odometer(self): """Print a statement showing the car's mileage.""" print("This car has " + str(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
electric_car.py
"""A set of classes that can be used to represent electric cars.""" from car import Car class Battery(): """A simple attempt to model a battery for an electric car.""" def __init__(self, battery_size=60): """Initialize the batteery's attributes.""" self.battery_size = battery_size def describe_battery(self): """Print a statement describing the battery size.""" print("This car has a " + str(self.battery_size) + "-kWh battery.") def get_range(self): """Print a statement about the range this battery provides.""" if self.battery_size == 60: range = 140 elif self.battery_size == 85: range = 185 message = "This car can go approximately " + str(range) message += " miles on a full charge." print(message) class ElectricCar(Car): """Models aspects of a car, specific to electric vehicles.""" def __init__(self, manufacturer, model, year): """ Initialize attributes of the parent class. Then initialize attributes specific to an electric car. """ super().__init__(manufacturer, model, year) self.battery = Battery()
ElectricCar 类需要访问其父类Car ,因此我们直接将Car 类导入该模块中
from car import Car from electric_car import ElectricCar my_beetle = Car('volkswagen', 'beetle', 2015) print(my_beetle.get_descriptive_name()) my_tesla = ElectricCar('tesla', 'roadster', 2015) print(my_tesla.get_descriptive_name())
从模块car 中导入了Car 类,并从模块electric_car 中导入ElectricCar 类。接下来,我们创建了一辆普通汽车和一辆电动汽车。这两种汽车都得以正确地创建:
2016 Volkswagen Beetle
2016 Tesla Roadster
正如你看到的,在组织大型项目的代码方面,Python提供了很多选项。熟悉所有这些选项很重要,这样你才能确定哪种项目组织方式是最佳的,并能理解别人开发的项目。一开始应让代码结构尽可能简单。先尽可能在一个文件中完成所有的工作,确定一切都能正确运行后,再将类移到独立的模块中。如果你喜欢模块和文件的交互方式,可在项目开始时就尝试将类存储到模块中。先找出让你能够编写出可行代码的方式,再尝试让代码更为组织有序。
七、Python标准库
Python标准库 是一组模块,安装的Python都包含它。你现在对类的工作原理已有大致的了解,可以开始使用其他程序员编写好的模块了。可使用标准库中的任何函数和类,为此只需在程序开头包含一条简单的import 语句。下面来看模块collections 中的一个类——OrderedDict
字典让你能够将信息关联起来,但它们不记录你添加键值对的顺序。要创建字典并记录其中的键—值对的添加顺序,可使用模块collections 中的OrderedDict类。OrderedDict 实例的行为几乎与字典相同,区别只在于记录了键值对的添加顺序。
from collections import OrderedDict favorite_languages = OrderedDict() favorite_languages['jen'] = 'python' favorite_languages['sarah'] = 'c' favorite_languages['edward'] = 'ruby' favorite_languages['phil'] = 'python' for name, language in favorite_languages.items(): print(name.title() + "'s favorite language is " + language.title() + ".")
从模块collections 中导入了OrderedDict 类然后创建了OrderedDict 类的一个实例,并将其存储到favorite_languages 中。请注意,这里没有使用花括号,而是调用OrderedDict() 来创建一个空的有序字典,并将其存储在favorite_languages 中
接下来,我们以每次一对的方式添加名字语言对,然后遍历favorite_languages ,将以添加的顺序获取调查结果:
Jen's favorite language is Python. Sarah's favorite language is C. Edward's favorite language is Ruby. Phil's favorite language is Python.
这是一个很不错的类,它兼具列表和字典的主要优点(在将信息关联起来的同时保留原来的顺序)。等你开始对关心的现实情形建模时,可能会发现有序字典正好能够满足需求。随着你对标准库的了解越来越深入,将熟悉大量可帮助你处理常见情形的模块。
八、编码规范
- 类名应采用驼峰命名法 ,即将类名中的每个单词的首字母都大写,而不使用下划线。
- 实例名和模块名都采用小写格式,并在单词之间加上下划线。
- 对于每个类,都应紧跟在类定义后面包含一个文档字符串。这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。每个模块也都应包含一个文档字符串,对其中的类可用于做什么进行描述。
- 可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。
- 需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的import 语句,再添加一个空行,然后编写导入你自己编写的模块的import 语句。在包含多条import 语句的程序中,这种做法让人更容易明白程序使用的各个模块都来自何方。