Python学习笔记

十八、类

2017-12-04  本文已影响25人  焰火青春

面向对象编程是最有效的软件编写方法之一

在面向对象编程中,编写表示现实世界中的事物和情景的类,并基于这些类来创建对象,根据类来创建对象被称为实例化,这让你能够使用类的实例;

理解面向对象编程有助于像程序员一样看世界,还可以帮助自己明白编写的代码,使你与其他程序员合作更轻松。

1.1、创建和使用类

使用类几乎可以模拟任何东西,类名必须大写,一个类由方法和属性组成,类中的函数称为方法,可以通过实例访问的变量称为属性

1.1.1、创建类

下面将创建一个dog 类,这个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() + ' roll over!')

上面列子中,init()方法,是一种特殊方法,包含了三个形参,self、name和age,self必不可少,而且必须在气体形参之前,python调用这个方法时,将自动传入实参self,每个与类关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能访问类中的属性和方法,调用Dog类的init方法时,通过实参向Dog()传递名字和年龄,self自动传递,不需要传递,因此只需给name和age提供值。

由一个类可以生成无数个对象,当一个对象的方法被调用的时候,对象会将自身的引用作为第一个参数传递给该方法。

以self 为前缀的变量,可以供类中所有方法使用,我们可以通过类的任何实例来访问这些变量,它们是类的属性。

1.1.2、根据类创建实例

根据类可以创建无数个实例对象(instance object),也称为类的实例化:

下面来创建一个表示特定的狗的实例对象:

class Dog():
  --snip--
  
my_dog = Dog('willie', 6)  # 类的实例化(实例对象),小写
print("My dog's name is " + my_dog.name.title() + '.')  # 访问类的属性name
print('My dog is ' + str(my_name.age) + ' years old.')  # 访问类的属性age
------
My dog's name is willie.
My dog is 6 years old.

访问类的属性

要访问类的属性,可以使用句点表示法,如要访问name的值,可以使用(my_dog.name)的方法。

调用类的方法

与访问属性一样,调用方法也可以使用句点表示法,如要调用sit()方法,可以使用(my_dog.sit() )

class Dog():
  --snip--
  
my_dog = Dog('willie', 6)
my_dog.sit()
my_dog.roll_over()
-------------

willie is now sitting
willie rolled over!

创建多个实例

根据类可以创建任意数量的实例,条件是将每个实例都存储到不同变量中,或占用列表或字典的不同位置,下面来创建一个名为your_dog 的实例:

class Dog():
  --snip--
  
my_dog = Dog('willie', 6)
your_dog = Dog('lucy', 3)
your_dog.sit()
---------------

lucy is now sitting.

1.2、使用类和实例

类创建后,大部分时间都是根据类创建实例,需要执行的一个重要的任务是修改实例的属性,修改属性的值可以直接修改或者以特定方式修改:

1.2.1、给属性指定默认值

类中属性都必须有初始值,哪怕是0或空字符串,在有些情况下,如设置默认值时,在方法init () 内指定的这种初始值是可行的,如果你对某个属性这样做了,就无需包含为它提供初始值的形参。

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.modle
    return long_name.title()
  
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
------
2016 Audi A4

接下来添加一个名为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     # 添加一个能存储汽车里程信息的属性,其属性默认值为0
    
  def get_descriptive_name(self):
    """返回整车的描述信息"""
    long_name = str(self.year) + ' ' + self.make + ' ' + self.modle
    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()          # 调用read_odometer() 方法,调用self.odometer_reading 属性的值
------
2016 Audi A4
This car has 0 miles on it.

1.2.2、修改属性的值

修改属性的值,有如下三种方法:

直接修改

将里程数(odometer_reading)修改为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.

通过方法修改属性的值

如果有替你更新属性的方法,就无需直接访问属性,而是将值传递给一个方法,由它在内部进行更新。

下面添加一个名为update_odometer() 的方法,其中有一个形参,用于接收里程数:

class Car():
  --snip--
  
def update_odometer(self, mileage):  # 定义一个方法,用于内部更新属性odometer_reading 的值,其中一个形参用于接收里程数,并将其指定为属性odometer_reading
  """将里程数读数设置为指定值"""
  """禁止里程往回拨"""
  if mileage >= odometer_reading:  # 修改属性前,检查指定的数是否合理
    self.odometer_reading = mileage
  else:
    print("You can't roll back an odometer!")
  
  
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(32)  # 调用方法,并传递值32给形参mileage
my_new_car.read_odometer()
------------

2016 Audi A4
This car has 32 miles on it.

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

有时需要将属性值递增特定的值,而不是设置为全新的值,比如买了一辆二手车,从购买到登记期间增加了100英里的里程:

class Car():
  --snip--
  def update_odometer(self, mileage):
    --snip--
    
  def increment_odometer(self, miles):  # 新增方法,接受一个单位为英里的数字,并将其存储到属性self.odometer_reading中
    """将里程表读数增加特定的量"""
    self.odometer_reading += miles
    
my_used_car = Car('subaru', 'outback', 2013)
print(my_used_car.get_rescriptive_name())

my_used_car.update_odometer(23500)
my_used_car.read_odometer()

my_used_car.increment_odometer(100)
my_used_car.read_odometer()

------
2013 Subaru Outback
This car has 23500 miles on it.
This car has 23600 miles on it.

练习

添加一个属性number_served,默认值为0,创建一个名为restaurant 的实例,打印这家餐馆有多少人在这家餐馆就餐,然后修改这个值并打印它,添加一个名为set_number_served() 的方法,它能设置就餐人数,调用它传递一个值,再打印这个值,添加一个名为increment_number_served() 的方法,它让你能够将就餐人数递增,调用它并传递一个这样的值:你认为这家餐馆每天可能接待的就餐人数:

class Restaurant():
    """创建一个Restaurant的类,里面包含餐厅名字,菜品类型"""
    def __init__(self, restaurant_name, cuisine_type):
        self.restaurant_name = restaurant_name.title()
        self.cuisine_type = cuisine_type
        self.number_served = 0

    def describe(self):
        """显示餐馆的基本信息"""
        msg = self.restaurant_name + ' serves wonderful ' + self.cuisine_type + '.'
        print(msg)

    def open_restaurant(self):
        """显示餐馆正在营业"""
        msg = self.restaurant_name + ' is open. Come on in!'
        print(msg + '\n')

    def set_number_served(self, number_served):
      """显示就餐人数"""
        self.number_served = number_served

    def increment_nums(self, additional_served):
      """人数递增"""
        self.number_served += additional_served


restaurant = Restaurant("alice's home", 'pizza')
restaurant.describe()
restaurant.open_restaurant()

restaurant.set_number_served(16)
print('The current number of repast is ' + str(restaurant.number_served))

restaurant.increment_nums(10)
print('The current number of repast is ' + str(restaurant.number_served))


------------------
Alice'S Home serves wonderful pizza.
Alice'S Home is open. Come on in!

The current number of repast is 16
The current number of repast is 26

1.3、继承

编写类时,不是都是从空白开始,有时要编写的类是另一个现成的类的特殊版本,可使用继承。一个类继承另一个类时,它将自动获得另一个类的所有属性和方法,被继承的类称为基类或父类,继承的类称为子类,子类也可以定义自己的属性和方法。

下面我们来创建一个名为 ElectricCar()的子类,继承于父类Car(),用来描述电动汽车的相关信息,它具备Car类的所有功能:

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_time = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_time.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

    def increment_odometer(self, miles):
        self.odometer_reading += miles


class ElectricCar(Car):         # 子类,定义子类时,括号内必须指定父类名称
    """电动车的独特之处"""

    def __init__(self, make, model, year):  # 方法__init__()接受创建父类实例所需信息
        """初始化父类的属性"""
        super().__init__(make, model, year) # super()是一个特殊函数,可以将父类与子类关联起来,它可以让python调用父类的方法__init__(),让子类实例包含父类的所有属性,父类也称为超类(superclass)


my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
------------------------

2016 Tesla Model S

上面例子,我们首先定一个名为ElectricCar的子类,它继承父类Car,创建子类时,父类必须在子类之前,定义子类时括号内必须指定父类名称,方法init() 接受Car实例所需的信息(初始化父类的所有属性和方法)。

super()函数是一个特殊函数,帮助python将父类与子类关联起来,自动找到父类的方法,不需要给出父类的名字,这行代码让python调用父类的方法init(),让子类实例包含父类的所有属性。

为子类创建一个实例对象my_tesla,传入实参,它将调用子类的init() 方法,子类的init() 方法将调用父类的init() 方法。

1.3.1、给子类定义属性和方法

继承后,可以添加区分子类和父类所需的新属性和方法,需要注意的是,子类可以添加任意数量的属性和方法,但只适用子类,如果要适用子类和父类,那么应该添加到父类中。

下面为子类ElectricCar 添加一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法,存储电瓶的容量,并编写一个打印电瓶描述的方法:

class Car():
  --snip--
  
class ElectricCar(Car):
  """"""
  def __init__(self, make, model, year):
    """"""
    super().__init__(make, model, year)
    self.battery_size = 70   # 添加新属性存储电瓶容量,初始值为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()  # 调用方法describe_battery()
------------------------

Tesla Model S 2016
This car has a 70-kwh battery.

1.3.2、重写父类的方法

如果父类的方法不能满足子类的需求,可以对其进行重写,为此可以在子类中定一个方法,与要重写的父类方法同名,这样python 就不会考虑这个父类的方法,而只关注这个子类中定义的相应方法。

假设Car类有一个名为fill_gas_tank()的方法,它对电动汽车来说没有意义,因此你可能需要重写它:

class Car():
  --snip--
  def fill_gas_tank(self):
    print('This car has a gas tank.')
    
class ElectricCar(Car):
  --snip--
  def fill_gas_tank(self):               # 重写父类方法fill_gas_tank(),因为父类的方法不能满足子类
    """电动车没有油箱"""
    print("This car doesn't need a gas tank!")
 
my_tesla = ElectricCar('tesla', 'model s' 2016)
my_new_car = Car('audi', 'a4', 2016)
my_tesla.fill_gas_tank()                # 父类的方法一旦被重写,父类的实例调用被重写的方法也会被忽略
my_new_car.fill_gas_tank()
------------------

This car doesn't need a gas tank!
This car doesn't need a gas tank!

1.3.3、将实例用作属性

使用代码模拟实物时,你可能会给类添加越来越多的细节,属性和方法以及文件越来越长,这种情况下可能需要将类的一部分作为一个独立的类提取出来,拆分成多个协同工作的小类。

给ElectricCar 类添加细节,可能会包含很多专门对汽车电瓶的属性和方法,可以将这些属性和方法提取出来,放到另外一个名为Battery的类中,并将Battery实例用作ElectricCar类的一个属性:

class Car():
  --snip--
  
class Battery():          # 定义一个新类,用于存储ElectricCar的属性和方法
  """一次模拟电动汽车电瓶的简单尝试"""
  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()  # 这行代码让python创建了一个新的Battery实例(由于没有指定参数,默认值为70),并将实例存储在属性self.battert中,每当__init__()被调用时,都将执行该操作
    
my_tesla = ElectricCar('tesla', 'model s', 2016)

print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery() # 调用时采用ElectricCar的实例加Battery实例的方式调用Battery中的方法
-------------------

2016 Tesla Model S
This car has a 70-kwh battery.

把子类拆分成很多小类去协同处理,看似多了很多步骤,但是可以避免子类混乱不堪,现在我们可以去拓展电动汽车的其他信息,比如描述电瓶容量的续航里程,在Battery中添加一个名为get_range ()的方法,用于描述续航里程:

class Car():
  --snip--
  
class Battery():
  --snip--
  def get_range(self):        # 定义一个方法,用于描述打印电瓶续航里程信息
    if self.battery_size == 70:
      range = 240
    elif self.battery_size == 85:
      range = 270
    
    msg = 'This car can go approximately ' + str(range)
    msg += ' miles on a full charge.'
    print(msg)
    
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_battery() 方法
----------------

2016 Tesla Model S
This car has a 70-kwh battery.
This car can go approximately 240 miles on a full charge.

1.4、导入类

随着我们给类不断添加功能,代码、文件越来越长,为遵循python的总体理念,应让文件尽可能简洁,为此我们可以将类存储到模块中,然后在主程序中导入所需的模块即可。

1.4.1、导入单个类

上面我们举例写了一个Car类,现在我们把它放到一个名为car.py的文件中(模块),再新建一个my_car.py 的文件,我们在my_car.py 中导入Car 类试试:

# 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 not 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_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_0 = 240
        elif self.battery_size == 85:
            range_0 = 270

        msg = 'This car can go approximately ' + str(range_0)
        msg += ' miles on a full charge.'
        print(msg)


class ElectricCar(Car):
    """电动车的独特之处"""

    def __init__(self, make, model, year):
        """初始化父类的属性,再初始化子类的属性"""
        super().__init__(make, model, year)
        self.battery = Battery()
# my_car.py                       
from car import Car                          # 从模块car.py中导入Car 类

my_new_car = Car('audi', 'a4', 2016)          # Car 类实例化,传入参数
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.

1.4.2、从一个模块中导入多个类

一个模块可以存储多个类,我们可以一次导入一个或者多个类到主程序中,

导入多个类时,类之间用逗号分隔即可,导入后即可根据类创建任意数量的实例。

如果我们要在同一个程序中创建普通汽车和电动汽车,就需要将Car 和 ElectricCar 类都导入:

# my_car.py
from car import Car, ElectricCar  # 从模块car.py中导入Car和ElectricCar 类

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()

my_tesla = ElectricCar('tesla', 'model s', 2016)
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()

1.4.3、导入整个模块

也可以导入整个模块,再使用句点表示法访问所需要的类,这种方法,代码易读,也不会与当前文件使用的任何名称发生冲突:

# my_car.py
import car

my_new_car = car.Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
------------------
2016 Audi A4

1.4.4、导入模块中所有类

# 语法
from module_name import *

不推荐使用这种方法导入模块中所有类,这样容易导致名称方面的困惑,引发难以诊断的错误,应使用导入模块名,再采用句点表示法的方法访问。

1.4.5、在一个模块中导入另一个模块

有时需要将类分散到多个模块中,以免模块太大,或在同一模块中存储不相关的类,将类存储到多个模块中时,你可能会发现一个模块中的类依赖与另一个模块中的类,这种情况下,可在前一个模块中导入必要的类。

下面,我们将Car 类存储到模块car.py 中,将Battery和 ElectricCar 类存储到模块 electric_car.py 中,在调用ElectricCar 类时需要依赖Car 类,因此我们可以在electric_car.py 中导入car.py。

# electric_car.py
"""一组可用于表示电动汽车的类"""

from car import Car       # ElectricCar需要访问其父类,因此我们直接将Car类导入到该模块中

class Battery():
  --snip--
class ElectricCar():
  --snip--
# my_car.py             # 在my_car模块中导入car和electric_car 模块
from car import Car
from electric_car import ElectricCar

1.5、Python标准库

Python标准库是一组模块,安装的python都包含它,现在对类有一定的了解,可以使用其他程序员编写好的模块,以及标准库中的任何函数和类,为此只需一句在程序中包含一条简单的imp 语句,下面来看模块collections中的一个类-----OrdereDict

OrdereDict 实例行为与字典几乎一致,区别在于它记录了添加键-值对的添加顺序,而字典不能。

# favorite_language.py
from collections import OrderedDict

favorite_languages = OrderedDict()

favorite_languages['jen'] = 'Python'
favorite_languages['sarah'] = 'c'
favorite_languages['edward'] = 'ruby'
favorite_languages['phil'] = 'java'

for name, languages in favorite_languages.items():
    print(name.title() + "'s favorite language is " + languages.title() + '.')
----------------------------

Jen's favorite language is Python.
Sarah's favorite language is C.
Edward's favorite language is Ruby.
Phil's favorite language is Java.

1.6、类编码风格

与类有关的编码风格问题,在编写复杂程序更应遵循:

练习

模块random 包含以各种方式生成随机数的函数,其中randint()返回一个位于指定范围内的整数,创建一个Die 类,包含一个名为sides 的属性,默认值为6,编写一个名为 roll_die()的方法,它打印位于1和骰子面数之间的随机数,创建一个6 面、10面、20面的骰子,再分别掷 10 次:

from random import randint        # 导入random 模块以及randint 函数

class Die():
  def __init__(self, sides=6):
    self.sides = sides
  
  def roll_die(self):
    return randit(1, self.sides)   # 返回生成1 到self.sides 间的随机数,不传入参数就使用默认值 6
  
d_6 = Die()                      # Die 类实例化,6 面骰子抛掷10次

results = []                                       # 将结果存储到 results 列表中
for roll_num in range(10):
  result = d_6.roll_die()
  results.append(result)
print('10 rolls of a 6-sided die:')
print(results)

d_10 = Die(sides=10)             # 10 面骰子

results = []
for roll_num in range(10):
    result = d_10.roll_die()
    results.append(result)
print('10 rolls of a 6-sided die:')
print(results)


d_20 = Die(sides=20)                     # 20 面骰子

results = []
for roll_num in range(10):
    result = d_10.roll_die()
    results.append(result)
print('10 rolls of a 6-sided die:')
print(results)
--------------------

10 rolls of a 6-sided die:
[4, 1, 1, 1, 5, 5, 2, 2, 3, 4]
10 rolls of a 6-sided die:
[5, 2, 2, 3, 9, 4, 2, 7, 10, 8]
10 rolls of a 6-sided die:
[1, 19, 5, 18, 19, 19, 8, 10, 8, 10]
上一篇下一篇

猜你喜欢

热点阅读