Python 面向对象编程

131 阅读7分钟

「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战」。

前言

Python 是一门自由的语言,我们可以用 Python 实现面向过程编程、函数式编程和面向对象编程。在实现一些小功能时,我们可以考虑使用面向过程编程或者函数式编程。但是从长远来看,在大项目上我们应该使用面向对象编程,这样编写的程序才更便于理解和管理。

在面向对象中,类是一个非常重要的角色。我们可以通过类来创建一个具体的对象,有了对象我们就能进行一系列的操作。而定义类的关键字是 class,下面我们定义一个人类:

class Person():
    
    def __init__(self):
        pass
    
    def run(self):
        pass
    
    def sleep(self):
        pass
    
    def eat(self):
        pass

上面我们定义了一个类。那么定义这个类有什么用呢?

类其实就是对象的模板,我们可以通过类知道这个类的对象的属性和行为。对于类的理解我们可以类比生物学的物种分类。我们根据不同的特性和行为给动物分类,把具有某些共同特性和行为(猫科动物所特点有的)的动物称作猫科动物,把拥有另一些共同特性和行为(犬科动物特有的)的动物称作犬科动物。

对象

对象不同于类,对象是通过类创造出来的东西,我们可以理解为用摸具创造出的物体。下面是通过 Person 类创建对象的操作:

# 创建一个 Person 的对象
p = Person()

Person 类规定了该类的属性和行为,但是我们通常只能通过类的对象对属性赋值,或者执行行为。类和对象的关系我们可以理解为“人类”和“爱因斯坦”的关系。人类是一个泛指,只要是具有某些特性和行为的物体我们都可以称为人类,而爱因斯坦是一个具体的人。他能吃饭,能思考,有性别。

我们可以通过下面的语句来执行对象的行为:

# 创建一个 Person 对象
p = Person()
# 执行对象的行为
p.run()

除了可以执行行为,我们也可以用来读取和修改对象的属性。

类变量和成员变量

成员变量

在上面的例子中,我们只给类定义了行为(方法),却没有定义属性。下面我们来看看要如何给一个类定义属性:

class Person():

    def __init__(self, name, gender, age):
        self.name = name
        self.gender = gender
        self.age = age
        
    def run(self):
        pass

    def sleep(self):
        pass

    def eat(self):
        pass

代码中,我们在 init 方法中添加了几句话。其中:

self.name
self.gender
self.age

都是该类的对象所具有的属性。而:

    def __init__(self, name, gender, age):

中的 name、gender、age 则是创建对象时需要传入的参数。想创建上面的类对象我们需要用如下语句:

p = Person('zack', 'male', 20)

其中 self 参数我们可以暂时忽略。

类变量

成员变量属于对象,是对象的属性。而类变量则是属于类本身,类变量的定义如下:

class Person():

    temp = 100

    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

    def run(self):
        pass

    def sleep(self):
        pass

    def eat(self):
        pass

print(Person.temp)

其中变量 temp 就是类变量。之前说过对象的属性需要通过对象才能获取,而类变量则可以通过类直接获取。

除了通过类获取外,我们也可以通过该类的对象获取,不过这种做法是不推荐的。

类方法

静态方法

上面提到的方法都有一个 self 参数,而且必须通过类对象来执行。而有一种和类变量相似的方法,可以直接通过类来调用,我们看下面代码:

class Person():

    temp = 100

    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

    def run(self):
        pass

    def sleep(self):
        pass

    def eat(self):
        pass

    @staticmethod
    def func():
        print('我是一个静态方法')

Person.func()

其中 func 方法都是静态方法,我们给 func 添加了一个 @staticmethod 装饰器。而且不需要给它传入 self 参数。

类方法

除了静态方法可以通过类直接调用,类方法也能直接通过类来调用。但是类方法的创建和静态方法有些不同,我们看下面的代码:

class Person():

    temp = 100

    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

    def run(self):
        pass

    def sleep(self):
        pass

    def eat(self):
        pass

    @classmethod
    def func(cls):
        print('我是一个静态方法')

Person.func()

其中 func 方法就是我们的类方法,类方法需要使用装饰器 @classmethod 装饰,另外类方法至少有一个参数 cls。

在之前见到的 self 参数实际上就是当前类对象,而 cls 参数则是当前类。

面向对象的三大特性

面向对象的三大特性分别是封装、继承和多态。而 Python 语言本身是弱类型的,所以多态在 Python 本身的语法中就有体现,所以我们会着重说封装和继承。

封装性

在我们之前的代码中,已经多次使用过封装了。我们回想一下,如果是用面向过程的方式,我们要怎么表示一个人?

表示的方式有很多,比如列表:

def run():
    print('run...')
    
def sleep():
    print('sleep...')
    
def eat():
    print('eat...')

p1 = [
    'zack',
    'male',
    20,
    run,
    sleep,
    eat
]

这种方式可以表示一个人的属性和行为,但是不是非常直观,而且如果我们要创建两个人就会比较麻烦。

我们还可以通过字典的方式表示一个人,这种方式要直观些,但是在创建两个人时还是很麻烦。这个时候我们就可以利用面向对象的封装性:

class Person():

    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

    def run(self):
        pass

    def sleep(self):
        pass

    def eat(self):
        pass

在上面的例子中,我们把人的属性和行为放入一个整体(类)。在创建对象时,我们不需要知道这个整体内部的样子就可以直接创建一个对象。

继承

在生物学中,生物被划分为界、门、纲、目、科、属、种。这些都是有层次关系的,比如猫科动物是一个分类,它属于哺乳纲。犬科动物也是一个分类,它同样属于哺乳纲。

类的定义同样可以有层次,想实现这种层次就需要使用到继承。下面我们先定义三个类:

class Animal():
    
    def __init__(self):
        pass
    
    def run(self):
        pass
    
    def sleep(self):
        pass
    
    def eat(self):
        pass
    
class Cat(Animal):
        
    def func1(self):
        pass
    
class Dog(Animal):
    
    def func2(self):
        pass

其中 Animal 中定义了三个方法,而 Cat 和 Dog 都属于动物,所以我让 Cat 和 Dog 继承 Animal 类。在 Cat 中只定义了 func1,而在 Dog 中只定义了 func2。下面我们可以测试一下:

cat = Cat()
cat.sleep()
cat.run()
cat.eat()
cat.func1()

dog = Dog()
dog.sleep()
dog.run()
dog.eat()
dog.func2()

我们创建了 Cat 对象和 Dog 对象,分别调用了自己内部定义的方法和 Animal 中定义的方法,发现都可以正常执行。这是因为 Cat 和 Dog 类继承了 Animal 的属性和行为。在 Python 中时允许多继承的。

多态

多态就是指对象的多种形态。在 Python 中对象本身就是多态的,比如下面这段代码:

def func(obj):
    obj.test()  

我们定义了一个函数,该函数接收一个参数。因为没有类型限制,所以我们可以传入任意的参数,但是在函数内部执行了一个 obj.test() 语句,要想程序不报错,我们需要传入一个内部包含 test 方法的参数。

这是程序对 obj 的唯一限制,因此 obj 可以有多种形态。在其它语言中,多态更多体现在继承和方法重写中。

魔法方法

在类中,有一些特殊的方法。比如 __init__,我们把这种 __方法名__ 的方法叫做魔法方法,当我们重写了一些特定的魔法方法后,我们的类就可以做一些特定的操作。如下面代码:

class Person():

    def __del__(self):
        print('删除')

    def __getitem__(self, item):
        print(item)

    def __add__(self, other):
        print(other)

p1 = Person()
p1[0]
p1 + 10
del p1

在 Person 类中,我们写了三个魔法方法,分别是 __del__、__getitem__ 和 __add__。下面分别说一下这几个方法的作用。

当我们执行:

del obj

时,程序就会调用 obj.__del__ 方法。当我们执行:

obj[item]

时,程序会执行 obj 的 __getitem__ 方法,并传入 item。当执行:

obj + item

时,程序会调用 __add__ 方法,并传入 item 参数。

除了上面这些魔法方法,Python 中还提供了许多其它魔法方法。你可以自己研究研究它们的作用。