类 类是什么,其实真正理解起来不是特别难,但是最重要的就是逻辑的架构。
如何设计出一个合适的类,是比较考验思维的。
上一节课就说了,python纵向可以分为变量-函数-类。
变量是python的基础单元,对变量的操纵就是在编写代码,而将一些代码封装起来命名就是函数,同理,将函数(方法)按照特定规则封装起来就称之为类。
提到了类,就不得不说一下两种编程方法:面向对象编程和面向过程编程。
面向对象编程是指面向对象注重的是某一对象的方法与属性,即该对象是什么,能够做什么。比较符合人的认知。
面向过程编程是指一事物从开始到结束的全过程,按照各个步骤,一步一步分析需要怎么做,期间可能涉及在多个对象之间的转换。
在面向对象编程中,你编写表现现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景,其逼真程度达到了令你惊讶的地步。
根据类创建对象被成为示例化,被创建的对象称之为类。
理解面向对象编程有助于你像程序员那样看世界,还可以帮助你真正明白自己编写的代码:不仅是各行代码的作用,还有代码背后更宏大的概念。了解类背后的概念可培养逻辑思维,让你能够通过编写程序来解决遇到的几乎任何问题。
创建和使用类 首先创建一个模拟小狗的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Dog (): """一次模拟小狗的简单尝试""" def __init__ (self, 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("bai" , 3 )"""创建实例""" my_dog.sit() my_dog.roll_over()"""----------------------------------------------""" Bai is now sitting. Bai rolled over!
类名按照规定,首字母大写。同时由于我们从头开始编写,因此类名后( )
为空白;
第二行为文档字符串,有助于理解;
在类中,函数被称作方法,但使用方法都是一样的,唯一的区别也只是调用方法;
第一个方法为默认方法,该方法存储类对象的属性。
其中方法名为__init__
(左右各两条短横线),这是一种约定。该方法每次运行类时自动运行。
形参中必须首先写self
这是将类中各个方法联结起来的关键。
其他形参与函数大致相同。最大的区别是,若形参具有默认值可不在_init_()
的括号内编写,而是直接写在该方法内部。
每当我们要根据类创建实例时,不用给self
传递实参。
剩下两个方法(函数)为类对象的通用动作。定义方法时,形参为self
,一定不能忘记。只有通过self
,才能调用self.name
和self.age
。将方法所代表的动作赋予给对象。
根据类可以创建任意多个实例。
再次创建一个模拟汽车的类(有默认值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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.
修改默认值的三种方式
1 2 3 4 5 6 7 8 9 class Car (): --snip-- """省略代码""" my_new_car = Car("audi" , 'a4' , 2016 )"""创建实例""" my_new_car.odometer_reading = 23 """修改默认值""" my_new_car.read_odometer()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Car (): --snip-- def update_odometer (self, mileage ): """将里程表读数设置为指定的值""" self.odometer_reading = mileage my_new_car = Car("audi" , 'a4' , 2016 ) my_new_car.update_odometer(34 )"""指定里程表读数""" my_new_car.read_odometer()"""---------------------------------------""" This car has 34 miles on it.
使用方法对属性的值进行递增
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Car (): --snip-- def update_odometer (): --snip-- def increment_odometer (self, miles ): """将里程表读数增加指定的值""" """通过对miles结合if语句限定其为正,防止回拨里程表""" self.odometer_reading += miles my_new_car = Car("audi" , 'a4' , 2016 )print (my_new_car.read_odometer()) my_new_car.update_odometer(34 )print (my_new_car.read_odometer()) my_new_car.increment_odometer(2500 )print (my_new_car.read_odometer())"""----------------------------------""" This car has 0 miles on it. This car has 34 miles on it. This car has 2534 miles on it.
继承 编写类时,并非总是从空白开始。如果你要编写的类是另一个现成类的特殊版本,可使用继承 。
一个类继承另一个类时,它将自动获得另一个类的所有属性和方法;原有的类称为父类 ,而新类称为子类 。
子类继承了父类的所有的属性和方法,同时还可以定义自己的属性和方法。
创建子类 1 2 3 4 5 6 7 8 9 10 class ElectrciCar (Car ): """电动汽车的独特之处""" def __init__ (self, make, model, year ): """初始化父类的属性""" super ().__init__(make, model, year) my_electriccar = ElectricCar('tesla' , 'model s' , 2016 )print (my_electriccar.get_descriptive_name())"""---------------------------------------------""" 2016 Tesla Model S
子类类名的( )
内为父类类名,父类必须包含在当前文件中,且位于子类前方;
方法__init__()
接受创建父类实例所需的信息(与父类相同);
super()
是一个特殊的函数,帮助python将父类和子类关联起来。让子类的实例包含父类的所有属性。父类又称超类(superclass),super()由此得名。
定义子类的属性和方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Car (): --snip--class ElectricCar (Car ): """电动汽车子类""" 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()
子类添加属性时,先初始化父类属性,再在__init__
中添加具有默认值的属性;
子类添加方法,按照类的原则正常添加。
重写父类 在子类继承父类时,由于子类的特殊性,可能有些父类的属性或方法是不适应于子类的,这时,子类需要在自己类中修改父类的某些属性和方法在自己类中的表现,这便是重写父类。
假设在父类Car中有一个名为fill_gas_tank()
的方法,它对全电动汽车来说毫无意义。因此,你需要在ElectricCar类中重写该方法,使其无效。下面演示一种重写方式:
1 2 3 4 5 6 7 8 9 10 class Car (): --snip--class ElectricCar (Car ): --snip-- def fill_gas_tank (): """电动汽车没有油箱""" print ("This car doesn't need a gas tank!" )
在子类中,对父类的方法进行重新定义后,优先执行子类中的方法。
将其他类的实例做为自己的属性 使用代码模拟实物时,你可能发现自己给类添加的细节越来越多:属性和方法清单都越来越长。在这种情况下,你可能需要将类的一部分作为一个独立的类提取出来。你可以将大型类拆分为几个协同工作的小类。
例如,在对电动汽车的模拟中,我们发现针对电瓶可以有很多方法和属性,则将电瓶独立出来成为一个类,然后在电动汽车中添加其电瓶属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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_descritive_name()) my_tesla.battery.describe_battery()
由于在子类中self.battery = Battery
的存在,当我们想要在ElectricCar类调用Battery.describe_battery()
时,需要写作self.battery.describe_battery()
。
模拟实物 在不断的使用编程模拟并解决现实事物时,我们经常会遇到一些有趣的问题。在这过程我们终将进入另一个境界:从较高的逻辑层面而非语法层面来考虑问题。
到达这个境界后,你经常会发现,现实世界的建模方法并没有对错之分。有些方法的效率更高,但要找出效率最高的表示法,需要经过一定的实践。
只要代码像你希望的那样运行,就说明你做的很好!即使你发现自己不得不多次尝试使用不同的方法来重写类,也不必气馁;要编写出高效、准确的代码,都得经过这样的过程。
导入类 导入类的方式在形式上与导入函数的方式相同。
1 2 3 4 5 6 7 8 9 from car import Car'''导入单个类''' from car import Car, ElectricCar'''从一个模块中导入多个类''' from car import *'''导入模块中所有类''' '''不常使用'''
自定义工作流程
在代码编写过程中每个人的编写习惯不同。
有人喜欢先尽可能在一个文件中完成所有的工作,确定一切都能正确运行后,再将类移到独立的模块中。
还有人习惯模块和文件的交互方式,在项目开始时就尝试将类存储到模块中。
找到适合自己的最为重要。
目前还没有编写过如此复杂的代码,因此还不能确定自己适合哪一种。按照以往惯例,大概率习惯先写在一个文件中写一部分,并确保运行。等到合适的时机,将其转移到模块中,然后继续在主文件中编写代码。
类编码风格 类名应采用驼峰命名法,即将类名中的每个首字母大写,而不使用下划线。实例名和模块都采用小写格式,并在单词之间加上下划线。
对于每个类,都应紧跟在类定义后面包含一个文档字符串。这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。
每个模块也都应包含一个字符串,对其中的类可用于做什么进行描述。
可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。
需要同时导入标准库中的模块和你的模块时,先导入标准库中的模块,然后添加一个空行,再导入你自己编写的模块。
小结 这一节讲的就是类,主要讲了以下几个方面:
类的定义,如何创建和使用类,如何修改默认值;
继承:如何编写继承、如何修改父类、如何将独立类作为另一类的属性;
导入类:导出类、类的某一方法、类的多个方法、类的全部方法到主程序或模块中;
类编码风格——驼峰命名法。