类
? 面向对象编程是最有效的软件编写方法之一。在面向对象编程中,编写表示现实世界中的事物和情景的类。并基于这些类来创建对象。编写类时,定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景。
? 根据类来创建对象被称为实例化。这让你能使用类的实例。你将指定可在实例中存储什么信息,定义可对这些实例执行哪些操作。你还将编写一些类来扩展既有类的功能,让相似的类能够高效的共享代码。你将自己编写的类存储在模块中,并在自己的程序文件中导入其他程序员编写的类。
? 理解面向对象编程有助于你像程序员那样看世界,还可以帮助你真正明白自己编写的代码。不仅是各行代码的作用,还有代码背后更宏大的概念。了解类背后的概念可培养逻辑思维,让你能够通过编写程序来解决遇到的几乎任何问题。
1.1 创建和使用类
? 使用类几乎可以模拟任何东西。下面编写一个表示小狗的简单Dog类。它表示的不是特定的小狗,而是任何的小狗。对于大多数宠物狗,我们都知道什么呢?宠物狗的名字和年龄。同时我们还知道,大多数的小狗都会蹲下和打滚。我们的Dog类将包含它们。
1.1.1 创建Dog类
? 根据Dog类创建的每个实例都将存储名字和年龄。我们赋予每条小狗蹲下(sit())和打滚(roll())的能力。
① class Dog():
② """一次模拟小狗的简单尝试"""
③ def __init__(self, name, age):
"""初始化属性name和age"""
④ self.age = age
self.name = name
⑤ def sit(self):
"""模拟小狗被命令时蹲下"""
print self.name.title() + " is sitting."
def roll(self):
"""模拟小狗被命令时打滚"""
print self.name.title() + " rolled over!"
? 这里需要注意的地方很多,但不必担心。在本章中充斥着这样的结构,你有大把的机会熟悉它。在①处,定义了一个名为Dog的类。根据约定,在Python中,首字母大写的名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。在②处,我们编写了一个文档字符串,对这个类的功能作了描述。
1.方法_init_()
? 类中的函数被称为方法。在前面学到的有关函数的一切都适用于方法,就目前而言,唯一重要的差别是调用方法的方式。③处_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()。由于这两个方法不需要额外的信息,如名字或年龄,因此它们只有一个形参self。
1.2 根据类创建实例
? 可将类视为有关如何创建实例的说明。Dog类是一系列的说明,让Python知道如何创建表示特定小狗的实例。
? 下面创建一个表示特定小狗的实例:
class Dog():
"""一次模拟小狗的简单尝试"""
def __init__(self, name, age):
"""初始化属性name和age"""
self.age = age
self.name = name
def sit(self):
"""模拟小狗被命令时蹲下"""
print self.name.title() + " is sitting."
def roll(self):
"""模拟小狗被命令时打滚"""
print self.name.title() + " rolled over!"
① my_dog = Dog('kitty', 3)
② print 'My dog name is ' + my_dog.name.title() + ' .'
③ print 'My dog age is ' + str(my_dog.age) + 'years old.'
? 这里使用前面编写的Dog类。在①处,我们让Python创建一条名字为"kitty",年龄为3的小狗。遇到这行代码的时候,Python使用实参"kitty"和3调用Dog()类中的方法_init_()。方法_init_()创建一个表示特定小狗的实例,并使用我们提供的值来设置属性name和age。方法_init_()并未显示的包含return语句,但Python自动返回一个表示这条小狗的实例。我们将这个实例存储在变量my_dog中。在这里,命名约定很重要。我们通常可以认为首字母大写的名称(如Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。
1.访问属性
? 要访问实例的属性,可使用句点表示法。在②处,我们编写了如下的代码来访问my_dog的属性name的值。
my_dog.name
? 句点表示法在Python中很常用,这种方法演示了Python如何获悉属性的值。在这里,Python先找到实例my_dog,再查找与这个实例相关联的属性name。在Dog类中引用这个属性时,使用self.name。
? 看一下上述代码的输出:
My dog name is Kitty .
My dog age is 3years old.
2.调用方法
? 根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法。下面让小狗蹲下和打滚。
my_dog.sit()
my_dog.roll()
? 要调用方法,可指定实例的名称(这里是my_dog)和要调用的方法,并用句点分隔它们。遇到代码my_dog.sit()时,Python在类Dog中查找方法sit()并运行其代码。Python会以同样的方式解读代码my_dog.roll()。
? 可以看到它按照我们的命令做了:
Kitty is sitting.
Kitty rolled over!
这种语法很有用。如果给属性和方法指定了合适的描述性名称,如name,age,sit()和roll(),即便是从未见过代码,我们也能轻松的推断出他是干什么的。
2.使用类和实例
? 你可以使用类来模拟现实世界中的很多情景。类编写好之后,你的大部分时间都将花在使用根据类创建的实例上。你需要执行的一个重要任务是修改实例的属性,你可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。
2.1 Car类
? 下面来编写一个表示汽车的类,它存储了有关汽车的信息,还有一些汇总的信息的方法:
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
③ my_new_car = Car("audi", "a4", 2020)
print my_new_car.get_descriptive_name()
? 在①处,我们定义了方法_init_()。与前面的Dog类中的一样,这个方法的第一个形参self。我们还在这个方法中包含了另外三个形参,make,model和year。方法_init_()接受这些形参的值,并将它们存储在根据这个类创建的实例和属性中。创建新的Car时,我们需要指定气制造商,型号和生产年份。
? 在②处,我们定义了一个名为get_descriptive_name()的方法,它使用属性year,make,model创建一个对汽车进行描述的字符串,让我们无需分别打印每个属性的值。为在这个方法中访问属性的值,我们使用了self.name,self.model,self.year。在③处,我们根据Car类创建了一个实例,并将其存储到变量my_new_car中。接下来调用方法get_descriptive_name(),指出我拥有的是一辆什么样的汽车。
2020 audi a4
? 为了让这个类更有趣,下面给它添加一个随时间变化的属性,它存储汽车的总里程。
2.2 给属性指定默认值
? 类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法_init_()内指定这种初始值。如果你对某个属性这样做了,就无需包含为它提供初始值的形参。
? 下面添加一个名为odometer_reading的属性,其初始值总是为0。在添加一个名为read_odometer()的方法,用于读取汽车的里程。
class Car():
"""模拟汽车的简单尝试"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
① self.odometer_reading = 0
def get_descriptive_name(self):
"""返回描述信息"""
long_name = str(self.year) + " " + self.make + " " + self.model
return long_name
② def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print "This car has " + str(self.odometer_reading) + " miles on it."
my_new_car = Car("audi", "a4", 2020)
print my_new_car.get_descriptive_name()
my_new_car.read_odometer()
? 现在,当Python调用方法_init_()来创建新实例时,将像前一个示例以属性的方式存储制造商等。接下来,Python将创建一个名为odometer_reading的属性,①处将其初始值设置为0。在②处,我们还定义一个名为read_odometer的方法,它让你能够轻松的获取汽车的里程数。
? 在一开始的时候,汽车的里程数为0。
2020 audi a4
This car has 0 miles on it.
? 但是在真正出售汽车时,为0里程的并不多,因此我们需要一个修改该属性的值的途径。
2.3 修改属性的值
? 可以以三种不同的方式修改属性的值,①直接通过实例进行修改,②通过方法进行设置,③通过方法进行递增(增加特定的值)。下面依次介绍这些方法。
1.直接修改属性的值
? 要修改属性的值,最简单的方式是通过实例直接访问它。下面的代码直接将里程数读书设置为100.
① my_new_car.odometer_reading = 100
my_new_car.read_odometer()
? 在①处,我们使用句点表示法来直接访问并设置汽车的属性odometer_reading。这行代码让Python在实例my_new_car中找到属性odometer_reading,并将该属性的值设置为100。
2020 audi a4
This car has 100 miles on it.
? 有时候需要像这样直接访问属性,但其他时候需要编写对属性进行更新的方法。
2.通过方法修改属性的值
? 如果有替你更新属性的方法,将大有裨益。这样,你就无需直接访问属性,而可将值传递给一个方法,由它在内部进行更新。
? 下面演示一个名为update_odometer()的方法
class Car():
"""模拟汽车的简单尝试"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""返回描述信息"""
long_name = str(self.year) + " " + self.make + " " + self.model
return long_name
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", 2020)
print my_new_car.get_descriptive_name()
② my_new_car.update_odometer(100)
my_new_car.read_odometer()
? 对Car类所做的唯一修改是在①处添加了方法update_odometer()。这个方法接收一个里程值,并将其存储到self.odometer_reading中。在②处,我们调用了update_odometer(),并向它传递了实参100(该实参对应方法定义中的形参mileage)。它将里程表的读数设置为100。
2020 audi a4
This car has 100 miles on it.
? 接着对方法update_odometer()进行扩展,使其在修改里程表读数时做些额外的工作。接下来添加一些逻辑,禁止任何人将里程表的读数往回调。
class Car():
"""模拟汽车的简单尝试"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""返回描述信息"""
long_name = str(self.year) + " " + self.make + " " + self.model
return long_name
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!"
my_new_car = Car("audi", "a4", 2020)
print my_new_car.get_descriptive_name()
my_new_car.update_odometer(100)
my_new_car.update_odometer(0)
my_new_car.read_odometer()
? 现在,update_odometer()在修改属性前先检查指定的读数是否合理。如果新指定的里程大于或等于原来的里程,将里程改为新指定的里程。否则我们就发出警告,指出不可以将里程往回拨。
2020 audi a4
You can't roll back an odometer!
This car has 100 miles on it.
3. 通过方法对属性的值进行递增
? 有时候需要将属性值递增特定的量,而不是将其设置为全新的值。假设我们购买了一辆二手车,且从购买到登记期间增加了100公里,下面的方法让我们能够传递这个增量,并相应的增加里程表读数。
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
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_old_car = Car("audi", "a4", 2020)
print my_old_car.get_descriptive_name()
③ my_old_car.update_odometer(100)
my_old_car.read_odometer()
④ my_old_car.increment_odometer(100)
my_old_car.read_odometer()
? 在①处,新增的方法increment_odometer()接收一个单位为里的数字,并将其加入到self.odometer_reading中。在②处,我们创建了一辆二手车,my_old_car。在③处,我们调用方法update_odometer()并传入100,将这辆二手车的里程表的读数设置为100。在④处,我们调用increment_odometer()并传入100,以增加从购买到登记期间的100里。
2020 audi a4
This car has 100 miles on it.
This car has 200 miles on it.
? 你可以轻松的修改这个方法,以禁止增量为负数,从而防止有人利用它来回拨里程表。
今天先写到这喽!下一篇开始从类的集成开始。
|