20  类和对象

20.1 类和对象介绍

class)是面向对象编程的基础。面向对象编程通过对客观世界和问题进行高度的抽象,极大简化了编程工作。

实际上,Python语言同时支持过程式以及函数式编程,完全可以在不定义类的情况下进行编程,但是不可能不使用类:在Python中,几乎所有数据都是以类的形式存在的:包括我们已经频繁使用的整型、浮点型、字符串、列表等等,都是类。

类是一个抽象的定义,包括了属性和方法。比如,在客观世界中,当我们提到「猫」这一类动物时时,一方面会通过品种、毛色、体重等描述猫,这些是所谓的属性property);而方法method)即猫的动作,比如猫可以吃饭、喝水、爬树等各种动作。

在Python中,属性即一个类中所定义的变量,而方法即类中定义的函数。在Python中,可以使用class关键字声明一个类:

class Cat:
    weight=8
    def get_weight(self):
        return self.weight
print(Cat.weight)
Cat.weight=9
print(Cat.weight)
8
9

以上通过class关键字定义了一个类,名字叫做Cat。Cat类有一个属性,称为weight,默认值为8;此外还有一个方法,称为get_weight(),这个方法所做的就是返回Cat类的weight属性。

以上定义的是类,经过定义后,还需要经过实例化,变成对象。注意类和对象的区别:类是一个抽象的、统一的集合,对象是类的具体化,可以具有差异性,比如:

class Cat:
    weight=8
    def get_weight(self):
        return self.weight
lucas=Cat()
lucas.weight=8.5
huahua=Cat()
huahua.weight=10
print("weight of lucas",lucas.get_weight())
print("weight of huahua",huahua.get_weight())
print("weight of cat class",Cat.weight)
weight of lucas 8.5
weight of huahua 10
weight of cat class 8

注意以上我们使用Cat()创建了一个新的cat对象的实例,或者对象:lucas和huahua,并分别修改了他们的weight属性,接着使用get_weight()方法获得了分别的weight属性,并将其打印出来。

注意对于对象的属性的修改不影响类的属性。

注意以上定义过程中的self关键字。self代表这个对象本身。在定义类时,为了使得对象能够调用自身的属性、方法等,都需要使用self关键字,所以在类里面定义方法时,第一个参数都是self。

此外,有的属性、方法可能只允许在类内部读写,而不允许外部读写,此时可以在属性或者方法名前面加两个下划线,比如:

class Cat:
    __weight=8.5
    def get_weight(self):
        return self.__weight
lucas=Cat()
try:
    print("weight of lucas",lucas.__weight)
except Exception as e:
    print(e)
lucas.__weight=9
print("weight of lucas",lucas.__weight)
print("weight of lucas",lucas.get_weight())
'Cat' object has no attribute '__weight'
weight of lucas 9
weight of lucas 8.5

从上面的运行结果中可以看到,__weight属性在类外是看不到的,强制读取会导致错误。但是如果直接写lucas.__weight,实际上是在lucas对象中新增了一个属性,而非内部的__weight属性。

但是如果我们调用get_weight()方法,由于该方法是类里面的成员,因而可以访问__weight属性。

最后,我们需要搞清楚在对象创建时发生了什么。以上我们使用Cat()创建了一个新的对象,但是我们实际上是没有没有创建Cat()函数的,那么这个创建是怎么执行的呢?

在Cat()调用时,Python会自动搜索类中定义的两个特殊函数:__new__()以及__init__(),并分别执行他们。__new__()函数用于创建对象,__init__()用于初始化对象,称为构造函数。与之对称的,还有__del__()函数,即当对象被删除时使用,称为析构函数。

一个例子:

class Cat:
    def __init__(self, name, weight=None, age=None):
        self.name=name
        self.__weight=weight
        self.__age=age
        print("cat ",self.name," is created.")
    def get_weight(self):
        return self.__weight
    def get_age(self):
        return self.__age
    def set_weight(self,weight):
        self.__weight=weight
    def set_age(self,age):
        self.__age=age

lucas=Cat('Lucas', weight=8.5, age=1.2)
print(lucas.get_age())
lucas.set_age(2.5)
print(lucas.get_age())
cat  Lucas  is created.
1.2
2.5

注意在以上代码中,在类Cat的定义中,我们额外定义了一个函数:__init__()函数,该函数除了self之外,还接受name, weight, age等参数。

而在创建对象时,我们使用的cat()函数的参数实际上就是__init__()函数的参数(除了self),当Cat()执行时,会默认将参数传给__init__(),执行该函数并创建一个新的对象。

在__init__()中,我们将weight和age两个参数赋值给了__weight、__age两个私有变量。在使用过程中,如果需要重新设置__weight、__age,需要使用set_weight、set_age两个函数。如果需要读取,需要使用get_weight、get_age两个函数。在面向对象编程中,类定义外不直接修改、读取属性,而是通过函数进行读取、修改是非常好的习惯。

20.2 继承

客观世界中有很多分类都是具有层级关系的,比如对生物的分类,就可以分门、纲、目、科、属、种,属于同一种类别的一般享有共同的特征和行为。比如,猫和虎都属于猫科动物,具有很多相同的特征,但是由于属于不同属,因而也有不同的特征。

在Python中,构造一个类时,可以声明该类继承了另外一个类,比如使用:

class Cat(Felidae):
    pass

就生命了一个类Cat,该类继承了类Felidae,即Cat类现在具有所有Felidae类的属性、方法。

在类的定义体中,可以使用super()函数获得其父类,因而如果我们需要调用父类的__init__()函数,只需要使用:super().__init__()就可以了。

除了继承父类的所有属性和方法外,子类还可以新增属性、方法,或者重新定义父类的属性、方法。

以下给出了一个例子:

class Felidae:
    def __init__(self, name, weight=None, age=None):
        self.name=name
        self.__weight=weight
        self.__age=age
        print("cat ",self.name," is created.")
    def get_weight(self):
        return self.__weight
    def get_age(self):
        return self.__age
    def set_weight(self,weight):
        self.__weight=weight
    def set_age(self,age):
        self.__age=age
    def catch(self):
        pass
        
class Cat(Felidae):
    def __init__(self, name, weight=None, age=None, color=None):
        super().__init__(name=name, weight=weight, age=age)
        self.color=color
    def shout(self):
        print("喵~")
    def catch(self):
        print("\N{rat}")

class Lion(Felidae):
    def __init__(self, name, weight=None, age=None, sex=None):
        super().__init__(name=name, weight=weight, age=age)
        self.sex=sex
    def catch(self):
        print("\N{rabbit}")
        
lucas=Cat("Lucas", color="三花", age=2)
lucas.catch()
lucas.shout()
print(lucas.get_age())
print("---------Simba---------")
simba=Lion('Simba', sex='male', weight=200)
simba.catch()
print(simba.get_weight())
cat  Lucas  is created.
🐀
喵~
2
---------Simba---------
cat  Simba  is created.
🐇
200

在上面的代码中,我们首先定义了一个类:Felidae,接着创建了Felidae的两个子类:Cat和Lion。

注意在Cat中我们定义了一个新的方法:shout(),该方法是父类Felidae中所没有的。

而Cat中的color以及Lion中的sex两个属性都是其父类中没有的。

此外,虽然在Felidae中定义了catch()方法,但是没有做任何操作,而在两个子类中,都重新定义了该方法。