面向对象编程

面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它将程序中的数据和操作这些数据的代码封装在对象中。Python是一种多范式编程语言,也支持面向对象编程,这篇笔记我们对Python中如何实现面向对象进行介绍。

定义和使用类

下面例子代码定义了一个简单的Python类,它的构造函数接收两个参数,类中还定义了一个say_hello()方法用来打印一些信息。

class Student(object):
    """
    学生类
    """

    def __init__(self, name: str, age: int):
        """
        初始化Student对象
        :param name: 学生姓名
        :param age: 学生年龄
        """
        self.name = name
        self.age = age

    def say_hello(self):
        """
        打印学生的Hello信息
        """
        print(f'Hello. I am {self.name}. I am {self.age} years old.')


tom: Student = Student("tom", 10)
tom.say_hello()

Python的类中,object是所有类的基类,我们可以继承该类。__init__()函数可以理解为类的构造方法,self指向对象实例,类似于其它语言的this,它是所有类实例方法的第一个参数。此外,Python中的实例属性也不是定义在类中的,而是在__init__()函数中动态绑定到对象的,这和Java等静态类型语言有所不同。

注:Python中类名需要使用CamelCase。

Python对象实例的属性也可以在构造函数外部动态绑定,下面是一个例子。

tom = Student('Tom', 18)
tom.score = 10
print(tom.score)

属性访问控制

Python中属性只有公开和私有两种访问权限,两个连续下滑线__开头的属性是私有的,私有属性不能在类的外部访问。

class Student(object):
    def __init__(self, name: str, age: int):
        self.__name = name
        self.__age = age

    def say_hello(self):
        print("I am", self.__name, ",", self.__age, "years old.")


tom: Student = Student("tom", 10)
tom.say_hello()

上面代码中,__name__age是私有属性,在类的外部我们不能访问它们。

继承和多态

Python中的类是支持继承的,且Python支持多继承,不过现代软件工程中多继承已经无数次被证明是个糟糕设计,实际开发中不建议使用多继承(原因包括复杂性增加、可读性降低、维护难度大等,甚至继承本身也要慎用,能用组合代替就不要用继承),因此我们这里只介绍单继承。

下面例子代码中,MaleStudent类继承了Student类并重写了say_hello()方法。

class Student(object):
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def say_hello(self):
        print("I am", self.name, ",", self.age, "years old.")


class MaleStudent(Student):
    def __init__(self, name: str, age: int):
        super().__init__(name, age)

    def say_hello(self):
        print("I am", self.name, ",", self.age, "years old boy.")


tom: Student = MaleStudent("tom", 10)
tom.say_hello()

代码中,子类使用super().__init__()调用了父类的构造方法并传入了构造参数,此外还覆盖了一个父类方法,实现了面向对象编程中的多态。注意子类使用self指针可以直接访问父类的非私有属性,就像是自己的属性一样。

类属性和静态方法

前面我们创建的类中使用的都是实例属性和实例方法,这些属性和方法都需要在类被实例化为对象后使用。Python中的类实际上也支持类属性和类方法,类属性直接定义在类内部而不是绑定到self上,类方法则需要使用@staticmethod装饰器标注,下面是一些例子。

class Foo(object):
    param: int = 1

    @staticmethod
    def add(a: int, b: int) -> int:
        return a + b


# 访问类属性
print(Foo.param)
Foo.param = 2
print(Foo.param)

# 调用类方法
print(Foo.add(1, 2))

我们可以看到类属性和类方法都不需要和self打交道,它们是类本身拥有的,而不是类的实例拥有的。访问类属性和类方法直接使用类名即可,而不需要将类实例化。

判断对象的类型

Python提供了两个函数type()isinstance()用来判断对象的类型,其中type()可以用来获取类类型,而isinstance()专用于判断对象是否属于某个类或其子类。

class Dog(object):
    pass


print(type(3))  # 输出 <class 'int'>
print(type(Dog()))  # 输出 <class '__main__.Dog'>

上面代码我们分别获取了整数和一个自定义类实例的类型。

class Animal(object):
    pass


class Dog(Animal):
    pass


dog1 = Dog()

print(isinstance(dog1, Dog))  # 输出 True
print(isinstance(dog1, Animal))  # 输出 True

上面代码我们使用isinstance()判断dog1是否为Dog类型或Animal类型,它们都是True,这是因为Dog类继承了Animal类,dog1Dog类的实例,自然也是Animal类的实例。

使用@Property装饰器

Python中我们可以访问类的公开属性,但有时我们不希望用户直接访问属性,而是需要通过Get/Set方法来访问属性。我们当然可以定义get_xxx()set_xxx()这种方式来实现,但这样写有些麻烦,Python提供了@Property来简化这一过程。

class Dog(object):

    def __init__(self, name: str):
        self.__name = name

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, name: str):
        self.__name = name


dog: Dog = Dog("Fido")
print(dog.name)
dog.name = "Rex"
print(dog.name)

代码中,我们没有直接访问__name属性,而是基于@property装饰器得到了类似Get/Set方法的效果,我们在Get/Set方法中可以进行更多的操作,比如添加校验逻辑、记录日志等。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。