python基础8

类是什么,其实真正理解起来不是特别难,但是最重要的就是逻辑的架构。

如何设计出一个合适的类,是比较考验思维的。

上一节课就说了,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!
  1. 类名按照规定,首字母大写。同时由于我们从头开始编写,因此类名后( )为空白;
  2. 第二行为文档字符串,有助于理解;
  3. 在类中,函数被称作方法,但使用方法都是一样的,唯一的区别也只是调用方法;
  4. 第一个方法为默认方法,该方法存储类对象的属性。
    1. 其中方法名为__init__(左右各两条短横线),这是一种约定。该方法每次运行类时自动运行。
    2. 形参中必须首先写self这是将类中各个方法联结起来的关键。
    3. 其他形参与函数大致相同。最大的区别是,若形参具有默认值可不在_init_()的括号内编写,而是直接写在该方法内部。
    4. 每当我们要根据类创建实例时,不用给self传递实参。
  5. 剩下两个方法(函数)为类对象的通用动作。定义方法时,形参为self,一定不能忘记。只有通过self,才能调用self.nameself.age。将方法所代表的动作赋予给对象。
  6. 根据类可以创建任意多个实例。

再次创建一个模拟汽车的类(有默认值)

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
  1. 子类类名的( )内为父类类名,父类必须包含在当前文件中,且位于子类前方;
  2. 方法__init__()接受创建父类实例所需的信息(与父类相同);
  3. 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()
  1. 子类添加属性时,先初始化父类属性,再在__init__中添加具有默认值的属性;
  2. 子类添加方法,按照类的原则正常添加。

重写父类

在子类继承父类时,由于子类的特殊性,可能有些父类的属性或方法是不适应于子类的,这时,子类需要在自己类中修改父类的某些属性和方法在自己类中的表现,这便是重写父类。

假设在父类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 *
'''导入模块中所有类'''
'''不常使用'''

自定义工作流程

在代码编写过程中每个人的编写习惯不同。

有人喜欢先尽可能在一个文件中完成所有的工作,确定一切都能正确运行后,再将类移到独立的模块中。

还有人习惯模块和文件的交互方式,在项目开始时就尝试将类存储到模块中。

找到适合自己的最为重要。

目前还没有编写过如此复杂的代码,因此还不能确定自己适合哪一种。按照以往惯例,大概率习惯先写在一个文件中写一部分,并确保运行。等到合适的时机,将其转移到模块中,然后继续在主文件中编写代码。

类编码风格

类名应采用驼峰命名法,即将类名中的每个首字母大写,而不使用下划线。实例名和模块都采用小写格式,并在单词之间加上下划线。

对于每个类,都应紧跟在类定义后面包含一个文档字符串。这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格式约定。

每个模块也都应包含一个字符串,对其中的类可用于做什么进行描述。

可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。

需要同时导入标准库中的模块和你的模块时,先导入标准库中的模块,然后添加一个空行,再导入你自己编写的模块。

小结

这一节讲的就是类,主要讲了以下几个方面:

  1. 类的定义,如何创建和使用类,如何修改默认值;
  2. 继承:如何编写继承、如何修改父类、如何将独立类作为另一类的属性;
  3. 导入类:导出类、类的某一方法、类的多个方法、类的全部方法到主程序或模块中;
  4. 类编码风格——驼峰命名法。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!