【Python基础】第十二课:类

类,创建类,类的基本用法,继承,导入类

Posted by x-jeff on April 2, 2020

本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.前言

Python中的类(Class)用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。⚠️对象是类的实例

2.创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Employee:
    'class information'
    empCnt=0

    def __init__(self,name,salary):
        self.name=name
        self.salary=salary
        Employee.empCnt+=1

    def displayCnt(self):
        print("Total Employee %d" % Employee.empCnt)

    def displayEmp(self):
        print("Name : ",self.name," , Salary : ",self.salary)
  • Employee为类名。
  • 'class information'为类文档字符串,存储类的帮助信息,可以通过Employee.__doc__查看。
  • __init__()被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。
  • displayCnt()displayEmp()为方法,即类内定义的函数。

3.类的基本用法

  1. :对具有相同数据和方法的一组对象的描述或定义。
  2. 对象:对象是一个类的实例。
  3. 实例(instance):一个对象的实例化实现。
  4. 实例属性(instance attribute):一个对象就是一组属性的集合。
  5. 实例方法(instance method):所有存取或者更新对象某个实例一条或者多条属性的函数的集合。
  6. 类属性(class attribute):属于一个类中所有对象的属性,不会只在某个实例上发生变化。
  7. 类方法(class method):那些无须特定的对象实例就能够工作的从属于类的函数。

3.1.self

❗️类的方法与普通的函数只有一个特别的区别:类的方法必须有一个额外的第一个参数名称,通常为self

1
2
3
4
5
6
7
8
class Test:
    def prt(self):
        print(self)

t=Test()
t.prt()
print(id(t))
print(hex(id(t)))

hex将十进制整数转为十六进制。

输出为:

1
2
3
<__main__.Test object at 0x10dce4128>
4526588200
0x10dce4128

可以看出tself的地址其实是一样的,也就是说self代表类的实例,代表当前对象的地址,而非类。

self不是python的关键字,也可以换成其他:

1
2
3
4
5
6
7
8
class Test:
    def prt(xxx):
        print(xxx)

t=Test()
t.prt()
print(id(t))
print(hex(id(t)))

结果是一样的。

3.2.__init__方法

__init__是一个特殊的方法,每当创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。

Employee类为例,我们将方法__init__()定义成了包含三个形参:selfnamesalary。⚠️在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面。

我们将通过实参向Employee()传递namesalaryself会自动传递,因此我们不需要传递它。每当我们根据Employee()类创建实例时,都只需给最后两个形参(namesalary)提供值。

3.3.创建实例对象

1
2
3
4
#创建Employee类的第一个对象
emp1=Employee("Zara",2000)
#创建Employee类的第二个对象
emp2=Employee("Manni",5000)

与C++类似,类对象需要通过点运算符.来访问类内成员:

1
2
3
emp1.displayEmp()
emp2.displayEmp()
print("Total Employee %d " % Employee.empCnt)

输出为:

1
2
3
Name :  Zara  , Salary :  2000
Name :  Manni  , Salary :  5000
Total Employee 2 

👉添加、删除、修改类的属性,例如:

1
2
3
4
emp1.age=7#为实例emp1添加一个'age'属性,emp2没有该属性
emp1.age=8#修改'age'属性
print(emp1.age)#输出为:8
del emp1.age#删除'age'属性

👉也可以使用以下函数访问属性:

  1. getattr(obj,name):访问对象的属性。
  2. hasattr(obj,name):检查是否存在某属性。
  3. setattr(obj,name,value):设置一个属性。如果属性不存在,会创建一个新属性。
  4. delattr(obj,name):删除属性。
1
2
3
4
setattr(emp2,'age',10)#相当于emp2.age=10
getattr(emp2,'age')#返回'age'属性的值,即10
hasattr(emp2,'age')#如果存在'age'属性则返回True
delattr(emp2,'age')#删除属性'age'

3.4.类的内置属性

  1. __name__:类的名字(字符串)。
  2. __doc__:类的文档字符串。
  3. __bases__:类的所有父类组成的元组。
  4. __dict__:类的属性组成的字典。
  5. __module__:类所属的模块。
  6. __class__:类对象的类型。

Employee类为例,每个内置属性的内容见下:

1
2
3
4
5
6
Employee.__doc__: class information
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {'__module__': '__main__', '__doc__': 'class information', 'empCnt': 2, '__init__': <function Employee.__init__ at 0x10f9d8d90>, 'displayCnt': <function Employee.displayCnt at 0x10f9d8e18>, 'displayEmp': <function Employee.displayEmp at 0x10f9d8ea0>, '__dict__': <attribute '__dict__' of 'Employee' objects>, '__weakref__': <attribute '__weakref__' of 'Employee' objects>}
Employee.__class__: <class 'type'>

4.继承

编写类时,并非总是要从空白开始。如果你要编写的类是另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,它将自动获得另一个类的所有属性和方法;原有的类称为父类(或基类超类),而新类称为子类(或派生类)。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。

4.1.子类的方法__init__()

我们首先新建一个表示汽车的类,它存储了有关汽车的信息,还有一个汇总这些信息的方法:

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
#electric_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't roll back an odometer!")
            
    def increment_odometer(self,miles):
        self.odometer_reading+=miles

下面来模拟电动汽车。电动汽车是一种特殊的汽车,因此我们可以在前面创建的Car类的基础上创建新类ElectricCar,这样我们就只需为电动汽车特有的属性和行为编写代码。

1
2
3
4
5
6
7
8
#electric_car.py
#接着父类继续写
class ElectricCar(Car):
    '''电动汽车的独特之处'''

    def __init__(self,make,model,year):
        '''初始化父类的属性'''
        super().__init__(make,model,year)

⚠️创建子类时,父类必须包含在当前文件中,且位于子类前面。

我们定义了子类ElectricCar,定义子类时,必须在括号内指定父类的名称。

super()是一个特殊的函数,帮助python将父类和子类关联起来。这条语句让python调用ElectricCar的父类的方法__init__()。也正是因为super(),父类也称为超类。

通过以下代码测试下子类是否成功继承了父类的方法:

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

输出为:

1
2016 Tesla Model S

4.2.给子类定义属性和方法

让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法。

1
2
3
4
5
6
7
8
9
10
11
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.")

为子类添加了新属性self.battery_size,并且父类Car实例都不包含它。此外,还添加了一个名为describe_battery()的方法。

4.3.重写父类的方法

对于父类的方法,只要它不符合子类的要求,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与要重写的父类方法同名。这样,python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。

4.4.将实例用作属性

在不断给ElectricCar类添加细节时,我们可能会发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,我们可将这些属性和方法提取出来,放到另一个名为Battery的类中,并将一个Battery实例用作ElectricCar类的一个属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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()

Battery类中,有个可选的形参battery_size:如果没有给它提供值,电瓶容量将被设置为70。

5.导入类

python允许将类存储在模块中,然后在主程序中导入所需的模块。

5.1.导入单个类

我们新建一个car.py,里面只存放Car类的代码。然后我们在my_car.py中导入Car类:

1
2
3
4
5
6
7
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()

输出为:

1
2
2016 Audi A4
This car has 23 miles on it.

5.2.在一个模块中存储多个类

除了Car类,我们将Battery类和ElectricCar类也放入car.py中。并在my_electric_car.py中导入ElectricCar类:

1
2
3
4
5
6
from car import ElectricCar

my_tesla=ElectricCar('tesla','model s',2016)

print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()

输出为:

1
2
2016 Tesla Model S
This car has a 70-kWh battery.

5.3.导入整个模块

1
2
3
4
5
6
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())

输出为:

1
2
2016 Volkswagen Beetle
2016 Tesla Roadster

5.4.导入模块中的所有类

导入模块中的所有类可用:

1
2
3
4
5
from car import *
my_beetle=Car('volkswagen','beetle',2016)
print(my_beetle.get_descriptive_name())
my_tesla=ElectricCar('tesla','roadster',2016)
print(my_tesla.get_descriptive_name())

import car的区别在于,from car import *不再需要前缀car.

不推荐这种方式,因为容易造成名称冲突。

5.5.在一个模块中导入另一个模块

例如将Car类存放在car.py中,将BatteryElectricCar类存放在electric_car.py中:

1
2
3
4
5
6
7
8
#electric_car.py
from car import Car

class Battery():
	#省略
	
class ElectricCar(Car):
	#省略

6.代码地址

  1. python中的类

7.参考资料

  1. Python3面向对象(菜鸟教程)
  2. 《Python编程-从入门到实践》Eric Matthes著;袁国忠译。