学习阶梯
《Python编程:从入门到实践》
第9章 类
面向对象编程是最有效的软件编写方法之一。
在面向对象编程中,编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。
编写类时,你定义一大类对象都有的通用行为。
基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。
根据类来创建对象被称为实例化,这让你能够使用类的实例。
可以指定能在实例中存储什么信息,定义可对这些实例执行哪些操作。还可以通过编写一些类来扩展既有类的功能,让相似的类能够高效地共享代码。将自己编写的类存储在模块中,并在自己的程序文件中导入其它程序员编写的类。
- 创建和使用类
使用类几乎可以模拟任何东西
- 创建dog类
dog.py
class Dog():
"""一次模拟小狗的简单尝试"""
def __init__(self,name,age):
"""初始化属性name和age"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗被命令时蹲下"""
print(self.name.title()+" is now sitting.")
def roll_over(self):
"""模拟小狗被命令时打滚"""
print(self.name.title()+" rolled over!")
根据约定,在Python中,首字母大写的名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。
1.1 方法__init__()
踩坑:init中的下划线是英文状态下,且init左右两侧各有两个,共4个,否则运行会报错
类中的函数称为 方法 。有关于函数的一切都适用于方法,就目前而言,唯一重要的差别是调用方法的方式。在__init__() 这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。
在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面。形参self必不可少的原因是Python调用这个__*init__()*方法来创建Dog实例时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
由于self会自动传递,因此我们不需要传递它,只需要传递定义的其他形参即可。以self为前缀的变量都可提供类中的所有方法使用,还可以用过类的任何实例来访问这些变量。通过实例访问的变量称为 属性 。
- 根据类创建实例
可将类视为有关如何创建实例的说明。
class Dog():
"""一次模拟小狗的简单尝试"""
def __init__(self,name,age):
"""初始化属性name和age"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗被命令时蹲下"""
print(self.name.title()+" is now sitting.")
def roll_over(self):
"""模拟小狗被命令时打滚"""
print(self.name.title()+" rolled over!")
my_dog=Dog('Willie',6)
print("My dog's name is "+my_dog.name.title()+".")
print("My dog is "+str(my_dog.age)+" years old.")
Python使用实参’Willie’和6调用Dog类中的方法__init__()。方法__*init__()*创建一个表示特定小狗的示例,并使用提供的值来设置属性name和age。方法__*init__()*并未显示地包含return语句,但Python自动返回一个表示这个条小狗的实例。
将这个实例存储在便令my_dog中。在这里,命定约定很有用:通常可以认为有首字母大写的名称(如dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。
1.2 访问属性
句点表示法在Python中很常用,这种语法演示了Python如何获悉属性的值。
1.3 调用方法
根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法。
class Dog():
"""一次模拟小狗的简单尝试"""
def __init__(self,name,age):
"""初始化属性name和age"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗被命令时蹲下"""
print(self.name.title()+" is now sitting.")
def roll_over(self):
"""模拟小狗被命令时打滚"""
print(self.name.title()+" rolled over!")
my_dog=Dog('Willie',6)
my_dog.sit()
my_dog.roll_over()
1.4 创建多个实例
可按需求根据类创建任意数量的实例。
class Dog():
"""一次模拟小狗的简单尝试"""
def __init__(self,name,age):
"""初始化属性name和age"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗被命令时蹲下"""
print(self.name.title()+" is now sitting.")
def roll_over(self):
"""模拟小狗被命令时打滚"""
print(self.name.title()+" rolled over!")
my_dog=Dog('Willie',6)
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()
可按照需求根据一个类创建任意数量的实例,条件是将每个实例都存储在不同的变量中,或占用列表或字典的不同位置。
练习
9-1 餐馆:创建一个名为 Restaurant 的类,其方法__init__()设置两个属性:restaurant_name 和 cuisine_type。创建一个名为 describe_restaurant()的方法和一个名为 open_restaurant()的方法,其中前者打印前述两项信息,而后者打印一条消息,指出餐馆正在营业。根据这个类创建一个名为 restaurant 的实例,分别打印其两个属性,再调用前述两个方法。
class Restaurant():
def __init__(self,restaurant_name,cuisine_type):
self.restaurant_name = restaurant_name
self.cuisine_type = cuisine_type
def describe_restaurant(self):
print("The restaurant's name is "+self.restaurant_name.title()+".")
print("The restaurant's cuisine type is "+self.cuisine_type.title()+".")
def open_restaurant(self):
print(self.restaurant_name.title()+" restaurant is opening!")
my_restaurant=Restaurant('agritainment','chinese meal')
my_restaurant.describe_restaurant()
my_restaurant.open_restaurant()
9-2 三家餐馆:根据你为完成练习 9-1 而编写的类创建三个实例,并对每个实例调用方法 describe_restaurant()。
class Restaurant():
def __init__(self, restaurant_name, cuisine_type):
self.restaurant_name = restaurant_name
self.cuisine_type = cuisine_type
def describe_restaurant(self):
print("The restaurant's name is " + self.restaurant_name.title() + ".")
print("The restaurant's cuisine type is " + self.cuisine_type.title() +
".")
def open_restaurant(self):
print(self.restaurant_name.title() + " restaurant is opening!")
one_restaurant=Restaurant('western','western-style food')
one_restaurant.describe_restaurant()
two_restaurant=Restaurant('french','french food')
two_restaurant.describe_restaurant()
three_restaurants=Restaurant('jade','chinese food')
three_restaurants.describe_restaurant()
9-3 用户:创建一个名为 User 的类,其中包含属性 first_name 和 last_name,还有用户简介通常会存储的其他几个属性。在类 User 中定义一个名为 describe_user()的方法,它打印用户信息摘要;再定义一个名为 greet_user()的方法,它向用户发出个性化的问候。创建多个表示不同用户的实例,并对每个实例都调用上述两个方法。
class User():
def __init__(self,first_name,last_name):
self.first_name = first_name
self.last_name = last_name
def describe_user(self):
print("User name is " + self.first_name.title() + " " +self.last_name + ".")
def greet_user(self):
print("Hello, " + self.first_name.title() + " " + self.last_name + "!")
user_1=User('marry','zhen')
user_1.describe_user()
user_1.greet_user()
user_2=User('jade','yang')
user_2.describe_user()
user_2.greet_user()
user_3=User('doris','zhang')
user_3.describe_user()
user_3.greet_user()
- 实用类和实例
修改实例的属性,可以直接修改,也可以编写方法以特定的方式进行修改。
- Car类
car.py
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())
- 给属性指定默认值
类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法__*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.")
my_new_car=Car('audi','a4','2016')
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
- 修改属性的值
可以以三种不同的而方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值)。
(1)直接修改属性的值
要修改属性的胡子,最简单的方式是通过实例直接访问它。
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.")
my_new_car=Car('audi','a4','2016')
print(my_new_car.get_descriptive_name())
#直接修改属性的值
my_new_car.odometer_reading=23
my_new_car.odometer_reading()
(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(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()
添加一些逻辑,禁止任何人讲里程表读数往回调
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self,make,model,year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading=10
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 oan odometer!")
my_new_car=Car('audi','a4','2016')
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
#直接修改属性的值
my_new_car.odometer_reading=23
my_new_car.read_odometer()
#通过方法修改属性的值
my_new_car.update_odometer(6)
my_new_car.read_odometer()
(3)通过方法对属性的值进行递增
有时候需要将属性值递增特定的量,而不是将其设置为全新的值。
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self,make,model,year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading=10
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 oan odometer!")
def incrment_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.incrment_odometer(100)
my_used_car.read_odometer()
attention: 使用类似于上面的方法来控制用户修改属性值(如里程表读数)的方式,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值。要确保安全,除了进行类似于前面的基本检查外,还需特别注意细节。
练习
9-4 就餐人数:在为完成练习 9-1 而编写的程序中,添加一个名为 number_served的属性,并将其默认值设置为 0。根据这个类创建一个名为 restaurant 的实例;打印有多少人在这家餐馆就餐过,然后修改这个值并再次打印它。
添加一个名为 set_number_served()的方法,它让你能够设置就餐人数。调用这个方法并向它传递一个值,然后再次打印这个值。
添加一个名为 increment_number_served()的方法,它让你能够将就餐人数递增。调用这个方法并向它传递一个这样的值:你认为这家餐馆每天可能接待的就餐人数。
class Restaurant():
def __init__(self, restaurant_name, cuisine_type):
self.restaurant_name = restaurant_name
self.cuisine_type = cuisine_type
self.number_served = 0
def read_number_served(self):
print("The number of people who have dined in this restaurant is " +
str(self.number_served))
def set_number_served(self,number):
self.number_served =number
def increment_number_served(self,add_number):
self.number_served+=add_number
sample_restaurant=Restaurant('western','western-style meal')
sample_restaurant.read_number_served()
sample_restaurant.number_served=18
sample_restaurant.read_number_served()
sample_restaurant.set_number_served(18)
sample_restaurant.read_number_served()
sample_restaurant.increment_number_served(2)
sample_restaurant.read_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。
class User():
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
self.login_attempts = 0
def increment_login_attempts(self):
self.login_attempts += 1
print("The value of Login attempt is " + str(self.login_attempts))
def reset_login_attempts(self):
self.login_attempts = 0
print("The value of Login attempt is " + str(self.login_attempts))
sample_user = User('marry', 'zhen')
sample_user.increment_login_attempts()
sample_user.reset_login_attempts()
sample_user.increment_login_attempts()
sample_user.increment_login_attempts()
sample_user.increment_login_attempts()
sample_user.reset_login_attempts()
- 继承
要编写的类是另一个现成类的特殊版本,可使用 继承 。
一个类继承另一个类时,将自动获得被继承类的所有属性和方法;原有的类称为父类,而新类称为子类。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。
- 子类的方法__init__()
创建子类的实例时,需要先给父类的所有属性赋值,子类的方法__*init__()*需要父类支持。
electric_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 oan odometer!")
def incrment_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())
创建子类时,父类必须包含在当前文件中,且位于子类前面。定义子列时,必须在括号内指定父类的名称。*super()*函数是一个特殊函数,将父类和子类关联起来。这行代码让Python调用ElectricCar的父类的方法__init__(),让ElectricCar实例包含父类的所有属性。父类也称为 超类(superclass),名称super因此得名。
- 给子类定义属性和方法
让一个类继承另一个类后,可添加区分子类和父类的所需的新属性和方法。
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 oan odometer!")
def incrment_odometer(self, miles):
self.odometer_reading += miles
class ElectricCar(Car):
"""Respresent 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_battary(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_battary()
对于子类的特殊化程度没有任何限制。模拟电动汽车时,可以根据所需的准确程度添加任意数量的属性和方法。如果一个属性或方法是任何汽车都有的,而不是电动汽车特有的,应将其加入到父类中而不是子类中,这样,使父类获得相应的功能,而子类只包含处理电动汽车特有属性和行为的代码。
- 重写父类的方法
对于父类的额方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。可在子类中定义一个这样的方法,即它与要重写的父类方法同名。Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。
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 oan odometer!")
def incrment_odometer(self, miles):
self.odometer_reading += miles
def fill_gas_tank(self):
"""车有油箱"""
print("This car has a gas tank!")
class ElectricCar(Car):
"""Respresent 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_battary(self):
"""打印一条描述电瓶容量的消息"""
print("This car has a "+str(self.battery_size)+"-kwh battery.")
#重写父类的方法
def fill_gas_tank(self):
"""电动汽车没有油箱"""
print("This ElectricCar doesn't need a gas tank!")
my_tesla=ElectricCar('tesla','model s',2016)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battary()
my_tesla.fill_gas_tank()
使用继承时,可让子类保留从父类那里继承而来的精华,并剔除不需要的糟粕。
- 将实例用作属性
当使用代码模拟实物时,随着添加的细节越来越多:属性和方法清单以及文件都越来越长。将类的一部分作为一个独立的类提取出来。将大型类拆分成多个协同工作的小类。
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()
class Battery():
"""一次模拟电动汽车电瓶的简单尝试"""
def __init__(self,battery_size=70):
"""初始化电瓶的属性"""
self.battery_size = battery_size
def describe_battary(self):
"""打印一条描述电瓶容量的消息"""
print("This car has a "+str(self.battery_size)+"-kwh battery.")
class ElectricCar(Car):
"""Respresent aspects of a car,specific to electric vehicles."""
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_battary()
my_tesla.battery.describe_battery() 这行代码让Python在实例my_tesla中查找属性battery,并对该属性中的Battery实例调用方法describe_battery()。
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()
class Battery():
"""一次模拟电动汽车电瓶的简单尝试"""
def __init__(self,battery_size=70):
"""初始化电瓶的属性"""
self.battery_size = battery_size
def describe_battary(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):
"""Respresent aspects of a car,specific to electric vehicles."""
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_battary()
my_tesla.battery.get_range()
- 模拟实物
模拟比较复杂的物件(如电动汽车)时,需要解决一些有趣的问题。续航里程是电瓶的属性还是汽车的属性呢?
在不同的环境或情况下,归类方向不一致。如果只需要描述一辆汽车,将方法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 实例,并调用这个方法。
class Restaurant():
def __init__(self, restaurant_name, cuisine_type):
self.restaurant_name = restaurant_name
self.cuisine_type = cuisine_type
def describe_restaurant(self):
print("The restaurant's name is " + self.restaurant_name.title() + ".")
print("The restaurant's cuisine type is " + self.cuisine_type.title() +
".")
def open_restaurant(self):
print(self.restaurant_name.title() + " restaurant is opening!")
class IceCreamStand(Restaurant):
def __init__(self, restaurant_name, cuisine_type):
super().__init__(restaurant_name, cuisine_type)
flavors = ['strawberry flavor', 'chocolate flavor', 'mocha flavor']
self.flavors = flavors
def display_flavor(self):
print("The flavors of ice cream are as follows:")
for flavor in self.flavors:
print(flavor.title())
sample_icecream = IceCreamStand('agritainment', 'chinese meal')
sample_icecream.display_flavor()
9-7 管理员:管理员是一种特殊的用户。编写一个名为 Admin 的类,让它继承你为完成练习 9-3 或练习 9-5 而编写的 User 类。添加一个名为 privileges 的属性,用于存储一个由字符串(如"can add post"、“can delete post”、"can ban user"等)组成的列表。编写一个名为 show_privileges()的方法,它显示管理员的权限。创建一个 Admin实例,并调用这个方法。
class User():
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
self.login_attempts = 0
def increment_login_attempts(self):
self.login_attempts += 1
print("The value of Login attempt is " + str(self.login_attempts))
def reset_login_attempts(self):
self.login_attempts = 0
print("The value of Login attempt is " + str(self.login_attempts))
class Admin(User):
def __init__(self, first_name, last_name):
super().__init__(first_name, last_name)
privileges = ['can add post', 'can delete post', 'can ban user']
self.privileges = privileges
def show_privileges(self):
print("The administrator's permissions are as follows:")
for privilege in self.privileges:
print("\t" + privilege)
sample_admin = Admin('Marry', 'jade')
sample_admin.show_privileges()
9-8 权限:编写一个名为 Privileges 的类,它只有一个属性——privileges,其中存储了练习 9-7 所说的字符串列表。将方法 show_privileges()移到这个类中。在 Admin类中,将一个 Privileges 实例用作其属性。创建一个 Admin 实例,并使用方法show_privileges()来显示其权限。
class User():
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
self.login_attempts = 0
def increment_login_attempts(self):
self.login_attempts += 1
print("The value of Login attempt is " + str(self.login_attempts))
def reset_login_attempts(self):
self.login_attempts = 0
print("The value of Login attempt is " + str(self.login_attempts))
class Privileges():
def __init__(self):
privileges = ['can add post', 'can delete post', 'can ban user']
self.privileges = privileges
def show_privileges(self):
print("The administrator's permissions are as follows:")
for privilege in self.privileges:
print("\t" + privilege)
class Admin(User):
def __init__(self, first_name, last_name):
super().__init__(first_name, last_name)
self.privileges = Privileges()
sample_admin = Admin('Marry', 'jade')
sample_admin.privileges.show_privileges()
9-9 电瓶升级:在本节最后一个 electric_car.py 版本中,给 Battery 类添加一个名为upgrade_battery()的方法。这个方法检查电瓶容量,如果它不是 85,就将它设置为 85。创建一辆电瓶容量为默认值的电动汽车,调用方法 get_range(),然后对电瓶进行升级,并再次调用 get_range()。你会看到这辆汽车的续航里程增加了。
class Car():
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
class Battery():
def __init__(self, battery_size=70):
self.battery_size = battery_size
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)
def upgrade_battery(self):
if self.battery_size !=85 :
self.battery_size =85
class ElectricCar(Car):
def __init__(self, make, model, year):
super().__init__(make, model, year)
self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2016)
my_tesla.battery.get_range()
my_tesla.battery.upgrade_battery()
my_tesla.battery.get_range()
- 导入类
随着不断给类添加功能,代码页幅会随之变长
为了让文件尽可能整洁,可以将类存储在模块中,在主程序中导入所需的模块
- 导入单个类
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 oan odometer!")
def incrment_odometer(self, miles):
"""将里程表读数增加指定的量"""
self.odometer_reading += miles
应该为创建的每个模块都编写文档字符串,对模块的内容做简单明了的描述
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()
两个文件需在同一目录下才能正确导入模块进行引用
导入类,不仅可以使用其功能,还使主程序变的更加简洁明了,逻辑清晰,更加高效的工作。
- 在一个模块中存储多个类
同一个模块中的类之间存在某种相关性会更好,但是也可以根据需要在一个模块中存储任意数量的类,这个看个人情况。
electric_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 oan odometer!")
def incrment_odometer(self, miles):
self.odometer_reading += miles
def fill_gas_tank(self):
"""车有油箱"""
print("This car has a gas tank!")
class Battery():
"""一次模拟电动汽车电瓶的简单尝试"""
def __init__(self,battery_size=70):
"""初始化电瓶的属性"""
self.battery_size = battery_size
def describe_battary(self):
"""打印一条描述电瓶容量的消息"""
print("This car has a "+str(self.battery_size)+"-kwh battery.")
#重写父类的方法
def fill_gas_tank(self):
"""电动汽车没有油箱"""
print("This ElectricCar doesn't need a gas tank!")
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):
"""Respresent aspects of a car,specific to electric vehicles."""
def __init__(self,make, model,year):
"""
初始化父类的属性,再初始化电动汽车特有的属性
"""
super().__init__(make, model,year)
self.battery=Battery()
my_electric_car.py
from electric_car import ElectricCar
my_tesla = ElectricCar('tesla', 'model s', '2016')
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battary()
my_tesla.battery.get_range()
踩坑
初始运行my_electric_car.py文件时报错
原因:引用的模块electric_car.py文件中存在之前定义对象,注释掉或删除定义的对象或实例化对象,即可正常运行。
可以这样理解,这里的模块相当于平时所说的库,调用库里的函数,库里一般没有自定义的对象,,所以这里也不应该添加自定义的对象,这样会导致错误。
我个人是忽略了这里的模块,相当于调用库里的函数,忘记这回事儿了,感谢大佬帮我找到自己忽略的原因。
- 从一个模块中导入多个类
可以根据需要再程序文件中导入任意数量的类。
electric_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 oan odometer!")
def incrment_odometer(self, miles):
self.odometer_reading += miles
def fill_gas_tank(self):
"""车有油箱"""
print("This car has a gas tank!")
class Battery():
"""一次模拟电动汽车电瓶的简单尝试"""
def __init__(self,battery_size=70):
"""初始化电瓶的属性"""
self.battery_size = battery_size
def describe_battary(self):
"""打印一条描述电瓶容量的消息"""
print("This car has a "+str(self.battery_size)+"-kwh battery.")
#重写父类的方法
def fill_gas_tank(self):
"""电动汽车没有油箱"""
print("This ElectricCar doesn't need a gas tank!")
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):
"""Respresent aspects of a car,specific to electric vehicles."""
def __init__(self,make, model,year):
"""
初始化父类的属性,再初始化电动汽车特有的属性
"""
super().__init__(make, model,year)
self.battery=Battery()
my_cars.py
from electric_car import Car,ElectricCar
my_beetle=Car('vollkswagen','beetle',2016)
print(my_beetle.get_descriptive_name())
my_tesla=ElectricCar('tesla','roadster',2016)
print(my_tesla.get_descriptive_name())
从一个模块导入多个类时,用逗号分隔各个类。
- 导入整个模块
electric_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 oan odometer!")
def incrment_odometer(self, miles):
self.odometer_reading += miles
def fill_gas_tank(self):
"""车有油箱"""
print("This car has a gas tank!")
class Battery():
"""一次模拟电动汽车电瓶的简单尝试"""
def __init__(self,battery_size=70):
"""初始化电瓶的属性"""
self.battery_size = battery_size
def describe_battary(self):
"""打印一条描述电瓶容量的消息"""
print("This car has a "+str(self.battery_size)+"-kwh battery.")
#重写父类的方法
def fill_gas_tank(self):
"""电动汽车没有油箱"""
print("This ElectricCar doesn't need a gas tank!")
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):
"""Respresent aspects of a car,specific to electric vehicles."""
def __init__(self,make, model,year):
"""
初始化父类的属性,再初始化电动汽车特有的属性
"""
super().__init__(make, model,year)
self.battery=Battery()
my_cars.py
import electric_car
my_beetle=electric_car.ElectricCar('vollkswagen','beetle',2016)
print(my_beetle.get_descriptive_name())
my_tesla=electric_car.ElectricCar('tesla','roadster',2016)
print(my_tesla.get_descriptive_name())
- 导入模块中的所有类
导入类的语法:
from module_name import *
不推荐使用这导入方式,因为不知道具体使用了模块的哪些类,不够明确;遇到相同名称的类也许会引发一些错误。
需要从一个模块中导入很多类时,最好导入整个模块,并使用module_name,class_name语法来访问类。这样可以清楚的知道在哪些地方导入了哪些函数、类、模块,避免了可能会出现的相同名称引发的错误。
- 在一个模块中导入另一个模块
将类分散到多个模块中,可以避免模块太大,同一个模块中也可能存储着不相关的类。将类存储在多个模块中时,一个模块中的类也许会依赖于另外一个模块中的类,可以在前一个模块中导入必要的类。
electric_car.py
from car import Car
class Battery():
"""一次模拟电动汽车电瓶的简单尝试"""
def __init__(self,battery_size=70):
"""初始化电瓶的属性"""
self.battery_size = battery_size
def describe_battary(self):
"""打印一条描述电瓶容量的消息"""
print("This car has a "+str(self.battery_size)+"-kwh battery.")
#重写父类的方法
def fill_gas_tank(self):
"""电动汽车没有油箱"""
print("This ElectricCar doesn't need a gas tank!")
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):
"""Respresent aspects of a car,specific to electric vehicles."""
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
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 oan odometer!")
def incrment_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())
- 自定义工作流程
一开始应让代码结构尽可能简单。在一个文件中确保正确运行后,再将类移到独立的模块。
喜欢模块和文件交互方式的,也可以一开始就将类存储到模块中。
先找出能够编写可以行的通的代码的方式,再试着让其变得更加组织有序。
练习
9-10 导入 Restaurant 类:将最新的 Restaurant 类存储在一个模块中。在另一个文件中,导入Restaurant 类,创建一个 Restaurant 实例,并调用 Restaurant 的一个方法,以确认 import 语句正确无误。
#模块:restaurant.py
class Restaurant():
"""一个可用于表示饭店的类"""
def __init__(self, restaurant_name, cuisine_type):
"""初始化描述饭店的属性"""
self.restaurant_name = restaurant_name
self.cuisine_type = cuisine_type
self.number_served = 0
def describe_restaurant(self):
"""返回饭店的描述信息"""
print("The restaurant's name is " + self.restaurant_name.title() + ".")
print("The restaurant's cuisine type is " + self.cuisine_type.title() +
".")
def open_restaurant(self):
"""打印饭店正在营业"""
print(self.restaurant_name.title() + " restaurant is opening!")
def set_number_served(self, number):
"""获取到就餐人数"""
self.number_served = number
def read_number_served(self):
"""打印在这个饭店吃晚餐的人数"""
print("The number of people who have dined in this restaurant is " +
str(self.number_served))
def increment_number_served(self, add_number):
"""将就餐人数增加指定量"""
self.number_served += add_number
#另一个文件导入:my_restaurant.py
from restaurant import Restaurant
sample_restaurant=Restaurant('New Moon','Chinese Restaurant')
sample_restaurant.describe_restaurant()
sample_restaurant.open_restaurant()
sample_restaurant.set_number_served(6)
sample_restaurant.read_number_served()
sample_restaurant.increment_number_served(2)
sample_restaurant.read_number_served()
9-11 导入 Admin 类:以为完成练习 9-8 而做的工作为基础,将 User、Privileges 和Admin 类存储在一个模块中,再创建一个文件,在其中创建一个 Admin 实例并对其调用方法 show_privileges(),以确认一切都能正确地运行。
#User、Privileges 和Admin 类存储在一个模块中:admin.py
class User():
"""一个可用于表示用户的类"""
def __init__(self, first_name, last_name):
"""初始化描述用户的属性"""
self.first_name = first_name
self.last_name = last_name
self.login_attempts = 0
def increment_login_attempts(self):
"""将登录次数增加指定量"""
self.login_attempts += 1
print("The value of Login attempt is " + str(self.login_attempts))
def reset_login_attempts(self):
"""重置登录次数"""
self.login_attempts = 0
print("The value of Login attempt is " + str(self.login_attempts))
class Privileges():
"""一个可用于表示权限的类"""
def __init__(self):
"""初始化权限具体内容"""
privileges = ['can add post', 'can delete post', 'can ban user']
self.privileges = privileges
def show_privileges(self):
"""打印权限的具体内容"""
print("The administrator's permissions are as follows:")
for privilege in self.privileges:
print("\t" + privilege)
class Admin(User):
"""一个可用于表示管理员的类"""
def __init__(self, first_name, last_name):
"""初始化管理员的权限"""
super().__init__(first_name, last_name)
self.privileges = Privileges()
#再创建一个文件:my_admin.py
from admin import User,Admin
sample_admin = Admin('Marry', 'jade')
sample_admin.privileges.show_privileges()
9-12 多个模块:将 User 类存储在一个模块中,并将 Privileges 和 Admin 类存储在另一个模块中。再创建一个文件,在其中创建一个 Admin 实例,并对其调用方法show_privileges(),以确认一切都依然能够正确地运行。
#将 User 类存储在一个模块中:user.py
class User():
"""一个可用于表示用户的类"""
def __init__(self, first_name, last_name):
"""初始化描述用户的属性"""
self.first_name = first_name
self.last_name = last_name
self.login_attempts = 0
def increment_login_attempts(self):
"""将登录次数增加指定量"""
self.login_attempts += 1
print("The value of Login attempt is " + str(self.login_attempts))
def reset_login_attempts(self):
"""重置登录次数"""
self.login_attempts = 0
print("The value of Login attempt is " + str(self.login_attempts))
#将 Privileges 和 Admin 类存储在另一个模块中:privileges.py
from user import User
class Privileges():
"""一个可用于表示权限的类"""
def __init__(self):
"""初始化权限具体内容"""
privileges = ['can add post', 'can delete post', 'can ban user']
self.privileges = privileges
def show_privileges(self):
"""打印权限的具体内容"""
print("The administrator's permissions are as follows:")
for privilege in self.privileges:
print("\t" + privilege)
class Admin(User):
"""一个可用于表示管理员的类"""
def __init__(self, first_name, last_name):
"""初始化管理员的权限"""
super().__init__(first_name, last_name)
self.privileges = Privileges()
#再创建一个文件:my_privileges.py
from privileges import Admin
sample_admin = Admin('Marry', 'jade')
sample_admin.privileges.show_privileges()
- Python标准库
Python标准库是一组模块,安装的Python都包含它。可使用标准库中的任何函数和类,只需在程序开头包含一条简单的import语句。
字典可以将信息关联起来,但是不会记录添加键-值对的顺序。要创建字典并记录其中的键-值对的添加顺序,可使用模块collections中的OrderedDict类,它与字典的区别只在于记录了键-值对的添加顺序。
favorite_languages.py
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()+".")
这里没有使用花括号,而是调用OrderDict()来创建一个空的有序字典。
OrderedDict类,兼具列表和字典的主要优点(在将信息关联起来的同时保留原来的顺序)
练习
9-13 使用 OrderedDict:在练习 6-4 中,你使用了一个标准字典来表示词汇表。请使用 OrderedDict 类来重写这个程序,并确认输出的顺序与你在字典中添加键—值对的顺序一致。
from collections import OrderedDict
word_list=OrderedDict()
word_list['print']='输出'
word_list['list']='列表'
word_list['tuple']='元组'
word_list['string']='字符串'
word_list['array']='数组'
word_list['integer']='整型'
word_list['float']='单精度浮点数'
word_list['double']='双精度浮点数'
word_list['key']='关键值'
word_list['value']='值'
print("\n编程词汇含义如下:")
for key, value in word_list.items():
print("\t"+key + " : " + value)
9-14 骰子:模块 random 包含以各种方式生成随机数的函数,其中的 randint()返回一个位于指定范围内的整数,例如,下面的代码返回一个 1~6 内的整数:
from random import randint
x = randint(1, 6)
请创建一个 Die 类,它包含一个名为 sides 的属性,该属性的默认值为 6。编写一个名为 roll_die()的方法,它打印位于 1 和骰子面数之间的随机数。创建一个 6 面的骰子,再掷 10 次。
创建一个 10 面的骰子和一个 20 面的骰子,并将它们都掷 10 次。
from random import randint
class Die():
def __init__(self,throw_times,sides=6):
self.throw_times= throw_times
self.sides=sides
def roll_die(self):
side=self.sides
throw_times=self.throw_times+1
print("打印位于1到"+str(side)+"之间的随机数:")
for times in range(1,throw_times):
number=randint(1,side)
print("\t"+"第"+str(times)+"次:"+str(number))
six_die=Die(10)
six_die.roll_die()
ten_die=Die(10,10)
ten_die.roll_die()
twenty_die=Die(10,20)
twenty_die.roll_die()
9-15 Python Module of the Week:要了解 Python 标准库,一个很不错的资源是网
站 Python Module of the Week。请访问 http://pymotw.com/ 并查看其中的目录,在其中找
一个你感兴趣的模块进行探索,或阅读模块 collections 和 random 的文档。
略
- 类编码风格
-
类名应采用驼峰命名法
- 类名中的每个单词的首字母都大写,而不使用下划线。
- 实例名和模块名都采用小写格式,并在单词之间加上下划线。
-
对于每个类,都应紧跟在类定义后面包含一个文档字符串。
- 这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。
- 每个模块也都应包含一个文档字符串,对其中的类可用于做什么进行描述。
-
可使用空行来组织代码,但不要滥用。
- 在类中,可使用一个空行来分隔方法;
- 而在模块中,可使用两个空行来分隔类。
-
需要同时导入标准库中的模块和你编写的模块时,
- 先编写导入标准库模块的import语句,再添加一个空行,
- 然后编写导入你自己编写的模块的import语句。