10. Python 面向对象编程#
Python 是一种支持面向对象编程(Object Oriented Programming, OOP)的语言。面向对象编程是一种编程范式,它将数据和操作数据的函数封装在对象中,通过类(Class)和对象(Object)来组织代码,使得代码更易于理解、维护、扩展和重用, 特别适合开发大型、复杂的系统。
10.1. 类, 对象, 属性, 方法#
面向对象编程中,类是对象的模板或蓝图,描述一类事物的属性(变量)和行为(方法);而对象是类的实例,具体体现类的属性和行为。例如,定义企业员工为一个类,该类中,可以包含姓名,工资,工龄,职务等属性;而具体的一个企业员工张三或李四就是这个类的一个实例。打个比喻,类就像是“蛋糕模具”,对象是用这个模具做出的“蛋糕”。
Python 通过 class
来定义一个类,并在类中进一步定义类的属性
(数据)和方法
(函数)。语法格式如下:
class ClassName: |
<statement_block_1> |
<statement_block_2> |
... |
<statement_block_N> |
下面的例子中,我们定义一个员工类:
class Employee:
# 类的属性
department = "business school" # 普通属性
__age = 20 # 前缀双下划线命名的变量为类的私有属性
# 类的方法
def get_age(self):
return self.__age
def say_hello(self):
print("Hello, everyone!")
john = Employee() # 实例化一个类
john.say_hello() # 访问对象里的方法
print("My department is %s." % john.department) # 访问对象里的属性
Hello, everyone!
My department is business school.
上面的例子中,我们定义了一个 Employee 类,有两个属性:一个普通属性和一个私有属性。通过实例化一个类,我们就可以通过实例访问类里面的一些属性和方法。
前缀双下划线命名的变量为类的私有属性,不能在类外部直接访问,但可以在类里面定义一个访问私有属性的方法,然后调用方法进行访问。
print("age is", john.get_age())
age is 20
如果直接在对象 john 访问私有属性 __age (例如 print(john.__age)),程序会报错。
print(john.__age)
Note
前缀双下划线命名的变量或函数为类的私有属性或方法,不能在类外部直接访问。
前缀单下划线命名的变量或函数为类的守保护属性或方法,可以在类外部直接访问,但不推荐访问。
10.2. 类的初始化 __init__#
类有一个名为 __init__() 的特殊方法,该方法在类实例化时会自动调用,进行对象的初始化。例如:
class Employee:
# 类的属性
department = "business school" # 普通属性
__age = 20 # 前缀双下划线命名的变量为类的私有属性
# 类的初始化方法
def __init__(self, name, age, department):
self.name = name
self.__age = age
self.department = department
# 类的方法
def get_age(self):
return self.__age
def say_hello(self):
print("Hello, everyone! My name is %s." % self.name)
john = Employee("John", 25, "engineering school") # 实例化一个类,自动调用 __init__()初始化
john.say_hello() # 访问类里的方法
print("My department is %s." % john.department) # 访问类里的属性
Hello, everyone! My name is John.
My department is engineering school.
上面的例子中,__init__ 方法初始化了类的多个属性,可以包括__init__ 方法前面未被类声明的属性。而已经声明的属性值被__init__ 方法中的参数值覆盖。
Note
类的方法与普通函数有一个显著的区别,它们的第一个参数总是 self,self 代表的是类的实例。
10.3. 类的继承与方法重写#
Python 支持类的继承,即一个类可以继承另一个类的所有属性和方法,还可以添加新的属性,新的方法或者重写另一个类的方法。
例如,我们可以添加一个类 NewEmployee,继承 Employ 类。
class NewEmployee(Employee): # 小括号内为所继承的父类名
under_probation = True # 子类自己的属性
def __init__(self, under_probation, *args, **kwargs):
super().__init__(*args, **kwargs) # 调用父类的初始化函数
self.under_probation = under_probation
# 重写父类的方法
def say_hello(self):
print("Hi! My name is %s." % self.name)
# 调用父类的方法
def age_range(self):
if super().get_age() >= 30: # 调用父类的方法
print("I am over 30s")
else:
print("I am under 30s")
# 自己的方法
def probabtion_info(self):
if self.under_probation:
print("I am under probation.")
else:
print("I have passed probation.")
tom = NewEmployee(
under_probation=False, name="John", age=25, department="engineering school"
)
tom.say_hello()
tom.probabtion_info()
Hi! My name is John.
I have passed probation.
上面的例子中,使用了super()
调用了父类的 __init__ 与 get_age 方法,并重写父类的 say_hello 方法。
Python 同样有支持多继承形式,即一个子类可以继承多个父类,将多个类的名称放入子类的小括号里面即可。例如,我们定义个 NewAcademicEmpolyee 类,同时继承了类 Employee 与 NewEmployee。
class NewAcademicEmployee(NewEmployee, Employee):
def say_hello(self):
print("Hello, I am a new academic empolyee.")
# 实例化子类时,初始化方法中的参数包括所有父类初始化方法中需要的参数
lily = NewAcademicEmployee(True, "Lily", 22, "art school")
lily.say_hello()
Hello, I am a new academic empolyee.
上面的例子中,子类在初始化时,需要传递所有父类初始化方法中需要的参数。
Note
如果子类和父类有同名的方法,并且你使用子类实例调用该方法,Python 会优先调用子类的方法,而不会调用父类的方法。
class Parent:
def show(self):
print("Parent method")
class Child(Parent):
def show(self): # 重写父类方法
print("Child method")
obj = Child()
obj.show() # 输出: Child method
Child method
10.4. 类的专有/特殊方法 __str__、__getitem__等#
以前后缀双下划线命名的方法为类的专有/特殊方法,又称作魔术方法,用以实现一些特定功能。
下面表格中总结了一些常用的专有方法。
专有方法 |
含义 |
---|---|
__init__ |
创建对象时自动调用,用于初始化类的实例 |
__str__ |
给用户看的,打印对象时显示的内容 |
__repr__ |
给开发者看的,用于调试,打印对象时显示的内容 |
__getitem__ |
获取对象中元素,使得对象可以像数组一样访问类中元素 |
__setitem__ |
设置对象中元素,使得对象可以像数组一样修改类中元素 |
__add__ |
实现对象的加法运算 |
__sub__ |
实现对象的减法运算 |
__mul__ |
实现对象的乘法运算 |
__truediv__ |
实现对象的除法运算 |
__eq__ |
自定义对象比较规则 |
__hash__ |
定义对象的哈希值,让对象支持 set 或 dict 存储 |
__class__ |
返回对象所属类的名字 |
__call__ |
让对象可以像函数一样被调用 |
下面的一个例子展示了 __repr__
与 __str__
的用法。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person('{self.name}', {self.age})"
def __str__(self):
return f"{self.name}, {self.age} years old"
p = Person("Bob", 30)
print(repr(p)) # 调用 __repr__
print(p) # 默认调用 __str__, 如果 __str__ 未定义,会默认调用 __repr__
Person('Bob', 30)
Bob, 30 years old
如果没有定义 __repr__ 或 __str__ 方法,则打印对象时会显示该对象的内存地址。
class Dog:
pass
d = Dog()
print(d) # 类中没有 __str__ 或 __repr__ 方法,打印显示地址
print(d.__class__) # 返回对象所属类的名字
<__main__.Dog object at 0x104ffbec0>
<class '__main__.Dog'>
Note
__main__ 代表的是当前运行的 Python 文件(以 .py 后缀命名的文件)
下面的例子,展示了如何通过 __getitem__ 方法实现像访问数组一样访问对象。
class MyList:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index] # 支持整数索引和切片
my_list = MyList([10, 20, 30, 40, 50])
print(my_list[1:3]) # 像数组切片一样访问类中元素
[20, 30]
下面的例子,实现了两个对象的相加。
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2) # 输出: Vector(6, 8)
Vector(6, 8)
下面的例子,展示了如何比较两个对象。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 若姓名与年龄都相同,表示两个实例相同
def __eq__(self, other):
if isinstance(other, Person):
return self.name == other.name and self.age == other.age
return False # 不是 Person 对象,直接返回 False
p1 = Person("Alice", 25)
p2 = Person("Alice", 25)
p3 = Person("Bob", 30)
print(p1 == p2) # True
print(p1 == p3) # False
True
False
下面的例子,实现了对象类似函数的调用。
class MyClass:
def __call__(self, *args, **kwargs):
print("Instance called!")
print("Arguments:", args, kwargs)
obj = MyClass()
obj(1, 2, a=3) # 像函数一样调用实例
Instance called!
Arguments: (1, 2) {'a': 3}
10.5. 类的特殊属性 __doc__、__name__ 等#
在 Python 中,类的特殊属性也称作“魔术属性”(magic attribute),或“dunder”属性/方法,通常指以双下划线开头和结尾的特殊属性或方法。这些属性和方法内置于 Python 语言中,用于实现一些特殊行为和功能。
除了专有方法,Python 还提供一些魔术属性,用于存储元数据或对象状态信息,如:
__doc__:存储模块、类或函数的文档字符串。
__name__:记录模块或类的名称。
__dict__:存放对象的属性字典,记录对象的所有可写属性。
class Example:
"""这是一个演示魔术属性的示例类。"""
def __init__(self, value):
self.value = value
def __str__(self):
return f"示例对象,值为: {self.value}"
def __repr__(self):
return f"Example({self.value!r})"
# 创建一个实例
ex = Example(42)
# 使用魔术属性
print(ex.__doc__) # 输出:这是一个演示魔术属性的示例类。
print(str(ex)) # 输出:示例对象,值为: 42
print(repr(ex)) # 输出:Example(42)
print(ex.__dict__) # 以字典形式输出实例的属性值
这是一个演示魔术属性的示例类。
示例对象,值为: 42
Example(42)
{'value': 42}
__name__
是 Python 内置的特殊变量,可以表示当前类的名称,也可以表示当前模块的名称。
MyClass.__name__ # 返回类的名称
'MyClass'
import numpy as np
np.__name__ # 返回模块的名称
'numpy'
当直接运行一个 Python 文件时,文件的名称为 ‘__main__’。因此,在编程调试某个程序文件时,可以用一个判断语句,根据文件的名字判断文件是否为当前直接运行的文件。例如:
# myscript.py
def hello():
print("Hello, world!")
if __name__ == "__main__":
print("This script is running directly")
hello()
else:
print("This script is imported as a module")
This script is running directly
Hello, world!
10.6. 类属性的相关方法setattr()
, getattr()
#
对于类中属性值的修改和访问,Python 内置有两个方法setattr()
, getattr()
比较有用。
setattr(object, name, value)
设置一个对象 object 的属性 atribte 的值为 value,若没有找到属性,则新建属性并赋值 valuegetattr(object, name)
返回对象 object的属性 name 的值,若没找到则触发 AttributeErrorgetattr(object, name, default)
返回对象 object 的属性 name的值,若没找到,则返回 default值
其中,小括号的属性参数以字符串形式表示。
class ExampleClass:
def __init__(self, name, age):
self.name = name
self.age = age
setattr(ExampleClass, 'name', 10)
print(getattr(ExampleClass, 'name'))
setattr(ExampleClass, 'gender', 'male') # 没有找到属性 gender,则新建该属性并赋值
print(getattr(ExampleClass, 'gender'))
p = ExampleClass('tom', '40')
print(getattr(p, 'grade', 75)) # 没有找到属性 grade,返回给定的值 75
print(p.gender)
10
male
75
male
10.7. 类方法与静态方法#
上面的例子中,类中方法的第一个参数总是 self,这类方法为普通方法
,只能被对象调用。
除了普通方法外,还有类方法
与静态方法
。
静态方法: 用 @staticmethod 装饰的不带 self 参数的方法叫做静态方法,类的静态方法可以没有参数,可以直接使用类名调用
类方法: 默认有个 cls 参数,可以被类和对象调用,需要加上 @classmethod 装饰器
class MyClass:
@staticmethod # 以@开头的名称为装饰器,用来说明特殊的函数
def fun():
print("静态方法")
@classmethod
def a(cls): # 第一个参数为 cls
print("类方法")
# 普通方法
def b(self): # 第一个参数为 self
print("普通方法")
MyClass.fun() # 静态方法可以被类名直接调用
MyClass.a() # 类方法可以被类名直接调用
case = MyClass()
case.fun() # 静态方法也可以被对象调用
case.a() # 类方法也可以被对象调用
case.b() # 普通方法只能被对象调用
静态方法
类方法
静态方法
类方法
普通方法
10.8. dateclass#
dataclass
是 Python 3.7 引入的装饰器,自动生成 __init__、__repr__、__eq__ 等方法,让数据类的编写更加简洁。
一个例子:
from dataclasses import dataclass
@dataclass
class Person:
name: str # 声明 name 为字符串类型
age: int # 声明 age 为整数类型
上面的代码等同于:
class Person:
def __init__(self, name: str, age: int): # 冒号跟数据类型,可以声明变量的数据类型
self.name = name
self.age = age
def __repr__(self):
return f"Person(name={self.name}, age={self.age})"
可以看出,dataclass 与普通类的一个区别是,必须在定义中声明属性的数据类型,但是显著减少了代码量。