四、类: 1. 类中对象:类中对象其实是一种自定义的数据结构(类比于字符串、字典等),既包含变量(习惯称为特性、属性),也包含代码(函数,也称为方法) #Python里的所有数据都是以对象形式存在,无论是简单的数据类型还是复杂的代码模块。然而,Python特殊的语法形式巧妙的将实现对象机制的大量 #细节隐藏了起来,输入num = 7 就可以创建一个值为7的整数对象,并且将这个对象赋值给变量num。在Python中,只有当你想要创建属于自己的对象或者 #需要修改已有对象的行为时,才需要关注对象的内部实现结构。 它是某一类具体事务的特殊实例。 类比概念:整数就是一个包含了加法、乘法之类方法的对象,属于一个公共的类。字符串也是Python对象,也有自己的字符串方法。 当你想要创建一个别人从来没有创建过的新对象时,首先必须定义一个类,用以表明该类型的对象所包含的特性和方法。 如果我们把对象比作塑料盒子,类则像制作盒子用的模具。 2. 根据类来创建对象被称为实例化 ,这让你能够使用类的实例。对象代表着一个独立的事物,它的方法则定义了它是如何与其他事物相互作用的。 3. 与模块不同,你可以同时创建许多同类的对象,它们的特性值可能各有不同。 3. 首字母大写的名称指的是类 例子1:创建Dog()类: class Dog(): '''一次模拟小狗的简单尝试''' def __init__(self, name, age): #_init_并不是必须的,只有当需要区分由该类创建的不同对象时,才需要指定_init_方法 '''初始化属性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!') 类中的函数称为方法,前面学到的有关于函数的一切都适用于方法。 a. 解释一下_init_方法 类中的函数是方法,前面学习到的有关函数的一切都适用于方法,_init_方法是一个特殊的方法,每当你根据Dog类创建新实例的时候, Python都会自动运行它。 b.上面的例子中_init_包含了三个形参:self、name、age,形参self必不可少,还必须位于其他形参前面。 当后面创建实例调用类的时候,比如说obj1=Dog('xiaohuang', '23'),self等于obj1,实参‘xiaohuang’、‘23’分别传入到了形参name、age里。 当执行obj2 = Dog ('xiaohei', '12')时,self等于obj2. 所以,内容其实被封装到了对象obj1和obj2中,每个对象都有name和age属性。 c. 那我们再解释一下对象和属性、类、方法、实例的含义和之间的关系 你可能经常听到一句话‘Python是面向对象的变成语言’,那这句话到底什么意思呢?Python里所有的数据-布尔值、整数、浮点数、字符串,甚至 大型数据结构、函数以及程序-都是以对象的形式存在。 Python是强类型的。 这里区分一下对象和变量,变量是在程序中为了方便的引用内存中的值而为它取的名字。变量是数据,不同类型的数据。 对象既包含数据(变量,或者特性,或者属性),也包含代码(函数,或者方法)。它是一类具体事物的特殊实例。 1)类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。 2)类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。 3)实例变量:定义在方法中的变量,只作用于当前实例的类。 4)实例化:创建一个类的实例,类的具体对象。 5)方法:类中定义的函数。 6)对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法 #!/usr/bin/python # -*- coding: UTF-8 -*- class Employee: '所有员工的基类' empCount = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.empCount += 1
def displayCount(self): print 'Total Employee %d' % Employee.empCount def displayEmployee(self): print 'Name : ', self.name, ', Salary: ', self.salary empCount 变量是一个类变量,它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用 Employee.empCount 访问。 第一种方法__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法 self 代表类的实例,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。 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 类。在❶处,我们让Python创建一条名字为'willie' 、年龄为6 的小狗。遇到这行代码时,Python使用实参'willie' 和6 调用Dog 类 中的方法__init__() 。方法__init__() 创建一个表示特定小狗的示例,并使用我们提供的值来设置属性name 和age 。方法__init__() 并未显式地包含return 语句, 但Python自动返回一个表示这条小狗的实例。我们将这个实例存储在变量my_dog 中。在这里,命名约定很有用:我们通常可以认为首字母大写的名称(如Dog )指的是类,而 小写的名称(如my_dog )指的是根据类创建的实例。 1)访问属性 my_dog.name 句点表示法在Python中很常用,这种语法演示了Python如何获悉属性的值。在这里,Python先找到实例my_dog ,再查找与这个实例相关联的属性name 。在Dog 类中引用这个属 性时,使用的是self.name 。我们使用同样的方法来获取属性age 的值。 2)调用方法 根据Dog 类创建实例后,就可以使用句点表示法来调用Dog 类中定义的任何方法 class Dog(): --snip-- my_dog = Dog('willie', 6) my_dog.sit() my_dog.roll_over() 3)可创建多个实例 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() 4. 使用类和实例 你可以使用类来模拟现实世界中的很多情景。类编写好后,你的大部分时间都将花在使用根据类创建的实例上。你需要执行的一个重要任务是修改实例的属性。你可以直接修改 实例的属性,也可以编写方法以特定的方式进行修改。 例子1:编写一个表示汽车的类 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 例子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): --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() 的方法,它让你能够轻松地获悉汽车的里程。 2016 Audi A4 This car has 0 miles on it. 例子3:修改属性的值 可以以三种不同的方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值)。 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-- 1 def update_odometer(self, mileage): '''彨棦掱昞.悢.抲.巜掕揑.''' self.odometer_reading = mileage my_new_car = Car('audi', 'a4', 2016) print(my_new_car.get_descriptive_name()) 2 my_new_car.update_odometer(23) my_new_car.read_odometer() 对Car 类所做的唯一修改是在1处添加了方法update_odometer() 。这个方法接受一个里程值,并将其存储到self.odometer_reading 中。在2处,我们调用 了update_odometer() ,并向它提供了实参23(该实参对应于方法定义中的形参mileage )。它将里程表读数设置为23;而方法read_odometer() 打印该读数: 2016 Audi A4 This car has 23 miles on it. 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() 5. 继承 1)在编写代码解决实际问题时,经常能找到一些已有的类,它们能够实现你所需的大部分功能,但不是全部。这时候利用类的继承。 从已有类中衍生出新的类,添加或修改部分功能。 2)一个类继承 另一个类时,它将自动获得另一个类的所有属性和方法;原有的 类称为父类 ,而新类称为子类 。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。 3)习惯上将原始的类称为 父类、超类或基类,将新的类称作孩子类、子类或衍生类。 4)定义子类的方法: 若已有的父类是Car,然后我们定义一个Car的子类Yugo,关键字class一样,不同的是需要在子类Yugo名字后面的括号里加上父类的名字。 >>> class Car(): pass >>> class Yugo(Car): pass 以上格式有一点需要关注:创建子类时,父类必须包含在当前文件中,且位于子类前面 class ElectricCar(Car): '''电动汽车的独特之处''' ❸ def __init__(self, make, model, year): '''初始化父类的属性''' ❹ super().__init__(make, model, year) super() 是一个特殊函数,帮助Python将父类和子类关联起来。这行代码让Python调用ElectricCar 的父类的方法__init__() ,让ElectricCar 实例包含父类的所 有属性。父类也称为超类 (superclass),名称super因此而得名。 6. 给子类定义属性和方法 让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法。 下面来添加一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法。我们将存储电瓶容量,并编写一个打印电瓶描述的方法: 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 类只包含处理电动汽车特有属性和行为的代 码。 7. 重写父类方法 对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与要重写的父类方法同名。这样,Python将不会考虑这 个父类方法,而只关注你在子类中定义的相应方法。 假设Car 类有一个名为fill_gas_tank() 的方法,它对全电动汽车来说毫无意义,因此你可能想重写它。下面演示了一种重写方式: class ElectricCar(Car): --snip-- def fill_gas_tank(): '''电动汽车没有油箱''' print('This car doesn't need a gas tank!') 现在,如果有人对电动汽车调用方法fill_gas_tank() ,Python将忽略Car 类中的方法fill_gas_tank() ,转而运行上述代码 8. 拆分小类 使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。 你可以将大型类拆分成多个协同工作的小类。 例如,不断给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() 9. 导入类 1)导入单个类 car是包含Car类的一个模块 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() 2)在一个模块中存储多个类 虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类。类Battery 和ElectricCar 都可帮助模拟汽车,因此下面将它们都加入模块 car.py中: 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() 3)从一个模块中导入多个类 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()) 4)导入整个模块 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()) 5)导入模块中所有类 from module_name import * 6)在一个模块中导入另一个模块 有时候,需要将类分散到多个模块中,以免模块太大,或在同一个模块中存储不相关的类。将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。 在这种情况下,可在前一个模块中导入必要的类。 例如,下面将Car 类存储在一个模块中,并将ElectricCar 和Battery 类存储在另一个模块中。我们将第二个模块命名为electric_car.py electric_car.py '''一组可用于表示电动汽车的类''' ❶ from car import Car class Battery(): --snip-- class ElectricCar(Car): --snip--A?R?'t |
|