一、概述
面向对象编程(oop, Object Oriented Programming)是最有效的软件编写方法之一。在面向对象的编程中,我们编写表示现实世界中的实物和情景的类,并基于这些类来创造对象。通过类来创造对象的过程被称为实例化,这让我们能够使用类的实例。
二、创建和使用类
使用类几乎可以模拟任何东西。
1、创建dog类
下面我们创建了一个dog类,根据dog类创建的每个实例都将存储名字和年龄,并且我们赋予了每条小狗蹲下(sit() )和打滚(roll_over() )的能力:
① 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)方法__init__()
类中的函数称为方法。
上面代码中的方法__init__()是一个特殊的方法,每当我们根据Dog类创建新实例时,Python都会自动运行它。
我们将方法__init__() 定义成了包含三个形参: self 、 name 和age 。其中,形参self必不可少,且位于最前面。在这个方法中,形参self必不可少的原因是:Python调用这个__init__() 方法来创建Dog 实例时, 将自动传入实参self 。 每个与类相关联的方法调用都自动传递实参self , 它是一个指向实例本身的引用, 让实例能够访问类中的属性和方法。(这个实参self其实就是实例化时使用的变量,如后面的my_dog)。
我们通过实参向Dog()传递名字和年龄;self会自动传递,我们只需传递后两个形参(name和age)提供值。
④处定义的变量都有self前缀,以self定义的变量可以供类中的所有方法使用,而且可以通过类的任何实例来访问这些变量。可以通过实例访问的变量称为属性。
2、根据类创建实例
创建一个表示特定小狗的实例:
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.")
注意:一般约定,首字母大写的名称(如Dog ) 指的是类, 而小写的名称(如my_dog ) 指的是根据类创建的实例。
(1)访问属性
要访问实例的属性,可使用句点表示法:
my_dog.name
(2)调用方法
根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法:
class Dog():
--snip--
my_dog = Dog('willie', 6)
my_dog.sit()
my_dog.roll_over()
(3)创建多个实例
可按需要根据类创建任意数量的实例。下面再创建一个名为your_dog的实例:
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()
输出:
My dog's name is Willie.
My dog is 6 years old.
Willie is now sitting.
Your dog's name is Lucy.
Your dog is 3 years old.
Lucy is now sitting.
三、使用类和实例
我们可以使用类来模拟现实世界中的很多情景。 类编写好后, 我们的大部分时间都将花在使用根据类创建的实例上。 我们需要执行的一个重要任务是修改实例的属性。 我们可以直接修改实例的属性, 也可以编写方法以特定的方式进行修改。
1、给属性指定默认值
类中的每个属性都必须有初始值, 哪怕这个值是0或空字符串。 在有些情况下, 如设置默认值时, 在方法__init__() 内指定这种初始值是可行的; 如果你对某个属性这样做了, 就无需包含为它提供初始值的形参。
如下面的名为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(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()
输出:
2016 Audi A4
This car has 0 miles on it.
2、修改属性的值
出售时里程表读数为0的汽车并不多,因此需要一个修改该属性的值的途径。
有三种方法修改属性的值:直接通过实例进行修改; 通过方法进行设置; 通过方法进行递增(增加特定的值) 。
(1)直接修改属性的值
这是最简单的方法,通过实例直接访问它。下面的代码直接将里程表读数设置为23:
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()
输出:
2016 Audi A4
This car has 23 miles on it.
(2)通过方法修改属性的值
下面的示例演示了一个名为update_odometer() 的方法:
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()
(3)通过方法对属性的值进行递增
有时候需要将属性值递增特定的量, 而不是将其设置为全新的值。假设我们购买了一辆二手车, 且从购买到登记期间增加了100英里的里程, 下面的方法让我们能够传递这个增量, 并相应地增加里程表读数:
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 中。
输出:
2013 Subaru Outback
This car has 23500 miles on it.
This car has 23600 miles on it.
三、继承
编写类时,不一定非要从空白开始。如果我们编写的类是另一个类的特殊版本,可使用继承。一个类继承另一个类时,他将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为子类。子类继承了父类的所有属性和方法,同时还可以定义自己的属性和方法。
1、子类的方法__init__()
在创建子类的实例时,Python首先给父类中的所有属性赋值。这就需要将子类中的方法__init__()与父类中的方法__init__()关联起来。使用super()函数。
下面我们模拟电动汽车。电动汽车是一种特殊的汽车,所以我们在之前car类的基础之上创建新类ElectricCar,这样我们就只需为电动汽车特有的属性和行为编写代码。
❶ 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调用父类(Car类)中的方法__init__(),让ElectricCar实例包含父类(Car类)中的所有属性。父类也称为超类,函数名super()由此得名。
为测试继承是否好用,在⑤处创建了一个ElectricCar的实例。
输出:
2016 Tesla Model S
2、给子类定义属性和方法
在上面的介绍中,我们只介绍了怎样继承父类的属性和方法。接下来介绍如何创建子类特有的属性和方法。
在下面的代码中,我们给子类ElectricCar添加一个特有的方法describe_battery():
class Car():
--snip--
class ElectricCar(Car):
"""继承Car类的属性,并添加电动车特有的属性"""
def __init__(self, make, model, year):
"""
电动汽车的独特之处
初始化父类的属性,再添加电动汽车特有的属性
"""
super.__init__(make, model, year)
① seif.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()
在①处,我们加了一个新属性;在②处,我们添加了一个新方法。
3、重写父类的方法
对于父类的方法,只要它不符合子类模拟的实物的行为,就可以进行重写。只需在子类中重写的方法与父类中的方法同名就可以了。
现在假设Car类中,有一个叫做fill_gas_tank()的方法,它对ElectricCar完全没用,所以可以对它进行重写:
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() , 转而运行上述代码。
4、将实例用作属性
在编写一个类时,随着添加的属性和方法越来越多,代码会越来越长,这时可以将这个类的一部分作为一个独立的类提取出来。
我们可以讲一个大型类拆分成几个协同工作的小类。
例如,我们在不断给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。
在ElectricCar 类中, 我们添加了一个名为self.battery 的属性(见❹) 。 这行代码让Python创建一个新的Battery 实例(由于没有指定尺寸, 因此为默认值70 ) , 并将该实例存储在属性self.battery 中。 每当方法__init__() 被调用时, 都将执行该操作; 因此现在每个ElectricCar 实例都包含一个自动创建的Battery 实例。
四、导入类
Python可以将类存储在模块中,然后在主程序中导入所需的模块。
1、导入单个类
注意:使用一个模块的程序的名称不能与该模块同名。例如:一个包含了Car类的模块命名为car.py,则使用这个模块的程序应使用更具体的名称,如:my_cay.py。
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
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()
①处,from car import Car,从car模块中调用Car类。
2016 Audi A4
This car has 23 miles on it.
另外,我们还可以在一个模块中存储多个类,以及从一个模块中导入多个类,还可以导入整个模块。
从一个模块中导入多个类:from car import Car, ElectricCar
导入整个模块:import car
还可以从一个模块中导入所有类,使用语法:from module_name import *,但不推荐使用。需要从一个模块中导入很多类时, 最好导入整个模块, 并使用 module_name.class_name 语法来访问类。
五、Python标准库
Python标准库是一组模块。跟上面所述的使用方法相同。
六、类编码风格
我们必须熟悉有些与类相关的编码风格问题, 在编写的程序较复杂时尤其如此。
类名应采用驼峰命名法 , 即将类名中的每个单词的首字母都大写, 而不使用下划线。 实例名和模块名都采用小写格式, 并在单词之间加上下划线。
对于每个类, 都应紧跟在类定义后面包含一个文档字符串。 这种文档字符串简要地描述类的功能, 并遵循编写函数的文档字符串时采用的格式约定。 每个模块也都应包含一个文档字符串, 对其中的类可用于做什么进行描述。
可使用空行来组织代码, 但不要滥用。 在类中, 可使用一个空行来分隔方法; 而在模块中, 可使用两个空行来分隔类。
需要同时导入标准库中的模块和你编写的模块时, 先编写导入标准库模块的import 语句, 再添加一个空行, 然后编写导入你自己编写的模块的import 语句。 在包含多条import 语句的程序中, 这种做法让人更容易明白程序使用的各个模块都来自何方。
以上内容基本来自于《Python编程:从入门到实践》