面向对象编程
面向对象编程(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类,dog1是Dog类的实例,自然也是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方法中可以进行更多的操作,比如添加校验逻辑、记录日志等。