0%

详解Python类的装饰器和魔法方法

介绍一下Python类的装饰器和魔法方法,以及参数的下划线前缀

一个记性并不好的人将在工作中学习到的碎片化的知识统筹起来必须要依靠记录,非常不幸的是鄙人在记忆力不强的前提下依然没有良好的记录习惯,加之跑通万岁的实用主义,使得写的代码总是看起来比较低级而缺乏优雅的气息,与本人气质强烈不符。痛定思痛,痛改前非,先从一个相对基础但极其重要的机制开始记起:Python中类的装饰器与魔法方法。

引言

在 Python 中,类是面向对象编程(OOP)的核心概念,它用于定义对象的蓝图。类封装了数据(属性)和行为(方法),通过类可以创建多个具有相同结构的实例对象。通过使用类,代码可以更具组织性和模块化,易于扩展和维护。

在 Python 中定义一个基本的类非常简单,使用关键字 class 进行定义。以下是一个简单的类定义的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyClass:
# 这是一个类属性
class_variable = "I am a class variable"

# 这是初始化方法(构造函数),当类实例化时会自动调用
def __init__(self, value):
# 这是实例属性
self.instance_variable = value

# 这是一个实例方法
def instance_method(self):
return f"This is an instance method and instance_variable is: {self.instance_variable}"

# 这是一个类方法,使用 @classmethod 装饰器定义
@classmethod
def class_method(cls):
return f"This is a class method and class_variable is: {cls.class_variable}"

# 这是一个静态方法,使用 @staticmethod 装饰器定义
@staticmethod
def static_method():
return "This is a static method"

在上述代码中,展现了类的各个组成部分为:

  • 类属性

    • 类属性是类级别的属性,它们对于所有实例都是共享的。类属性通常定义在类体中,独立于任何方法。
    • 在上例中,class_variable 就是一个类属性,所有对象共享同一个值。
  • 实例属性

    • 实例属性是在类的实例化过程中通过 __init__() 方法(也叫构造函数)动态创建的,每个实例有独立的属性值。
    • 通过 self.instance_variable = value,我们为每个实例赋予不同的 instance_variable 值。
  • 实例方法

    • 实例方法是类中定义的普通方法,它们操作实例属性,且必须接受参数 selfself 代表的是实例本身。
    • 实例方法通常用于定义对象的行为。
  • 类方法

    • 类方法是作用于类本身的方法,使用 @classmethod 装饰器定义。它接收 cls 参数,表示类对象,而不是实例对象。
    • 类方法可以访问和修改类属性。
  • 静态方法

    • 静态方法是类中的方法,但它既不依赖类属性也不依赖实例属性。静态方法使用@staticmethod 装饰器定义,通常用于执行与类相关但不需要访问类或实例的逻辑。

通过实例化类,可以获得对象:

1
2
3
# 实例化类,创建对象
obj1 = MyClass("Hello")
obj2 = MyClass("World")
  • 是抽象的模板,用于创建对象,它定义了对象的属性和行为。
  • 对象 是类的实例,每个对象都有自己独立的属性值和行为表现。

类的继承(Inheritance)

继承是面向对象编程的核心概念之一,它允许一个类继承另一个类的属性和方法,从而避免代码重复,并能够对现有的类进行扩展。被继承的类称为父类(基类或超类),继承的类称为子类(派生类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 定义父类
class Animal:
def __init__(self, name):
self.name = name

def speak(self):
return f"{self.name} makes a sound."

# 定义子类,继承父类 Animal
class Dog(Animal):
def speak(self):
return f"{self.name} barks."

class Cat(Animal):
def speak(self):
return f"{self.name} meows."

# 实例化子类
dog = Dog("Buddy")
cat = Cat("Whiskers")

print(dog.speak()) # 输出: Buddy barks.
print(cat.speak()) # 输出: Whiskers meows.

继承的特点:

  • 子类继承父类的所有属性和方法,并且可以重写(override)父类的方法。
  • 可以通过 super() 调用父类的方法。
  • 支持多重继承,子类可以从多个父类继承。

类的多态(Polymorphism)

多态允许不同的类实现相同的方法,而表现出不同的行为。它是一种编程能力,能够让相同的接口对不同的数据类型做出不同的响应,具体行为由具体的对象来决定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal:
def speak(self):
raise NotImplementedError("Subclass must implement this method.")

class Dog(Animal):
def speak(self):
return "Woof!"

class Cat(Animal):
def speak(self):
return "Meow!"

def animal_sound(animal):
print(animal.speak())

dog = Dog()
cat = Cat()

animal_sound(dog) # 输出: Woof!
animal_sound(cat) # 输出: Meow!

抽象类(Abstract Class)

抽象类是不能被实例化的类,它用于定义方法的模板,而子类必须实现这些方法。Python 通过 abc 模块中的 ABC 类和 abstractmethod 装饰器来实现抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from abc import ABC, abstractmethod

class Animal(ABC):
@abstractmethod
def speak(self):
pass

class Dog(Animal):
def speak(self):
return "Woof!"

class Cat(Animal):
def speak(self):
return "Meow!"

# animal = Animal() # 这会抛出错误,因为抽象类不能实例化
dog = Dog()
print(dog.speak()) # 输出: Woof!
  • 抽象类定义了抽象方法,子类必须实现这些抽象方法。
  • 抽象类提供了代码结构的基础框架,具体实现由子类负责。

组合(Composition)

组合是一种设计原则,通过将对象的实例作为其他类的属性来实现。这种方法允许类之间的松散耦合。相比继承,组合通常提供了更大的灵活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Engine:
def start(self):
return "Engine started"

class Car:
def __init__(self):
self.engine = Engine() # 将 Engine 类实例作为 Car 的属性

def start(self):
return self.engine.start()

car = Car()
print(car.start()) # 输出: Engine started

装饰器

装饰器的定义

在 Python 中,装饰器(Decorator)是一种函数,允许你在不修改原有函数代码的情况下,动态地增加或修改该函数的功能。简单来说,装饰器是用来"包装"其他函数的函数,它允许你在运行时增加函数的功能,通常用于日志记录、权限检查、缓存、计时等功能。

装饰器本质上是一个函数,它接受一个函数作为输入,并返回一个新的函数(通常是原函数的增强版本)。这种方式可以帮助我们在不改变原函数代码的前提下,对其进行修改或扩展。

在 Python 中,装饰器使用 @decorator_name 语法应用到一个函数上。这个语法等价于调用装饰器函数并将目标函数作为参数传递给它。

下面是一系列针对装饰器的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import time
import functools

# 定义一个装饰器函数decorator
def decorator(func):
# 我们用函数wrapper来代表被装饰后的func函数,最后返回wrapper
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f'{end_time - start_time}')
return result
return wrapper

# 上面这段代码在wrapper函数外面包裹了一个装饰器函数decorator,
# 使得wrapper函数的运行时间可以被检测。(*args, **kwargs)这两个参数可以代表传入的一切参数。
# 我们来看看如何使用它:

# 先定义一个square函数:
def square(a, b):
return a*b

# 随后,使用装饰器装饰他,生成一个新的函数
decorated_square = decorator(square)
#print(decorated_square(10,10))

# 实际上,python提供了一个更简单的方法:
@decorator
def square(a, b):
return a*b
# 这样,直接执行square(10,10),就能输出运行的时间

# 当然,我们也可以定义一个装饰器生成器:
def timer(threshold):
def decorator(func):
# 这是一个python中自带的装饰器,可以使得wrapper函数继承func的名字等
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
if start_time - end_time > threshold:
print("超时")
return result
return wrapper
return decorator

# 装饰这个函数
@timer(0.1)
def sleep_04():
time.sleep(0.4)

# sleep_04()

# 或者生成一个新函数
sleep_04 = timer(0.1)(sleep_04)
print(sleep_04.__name__)

装饰器概念:用来装饰其他函数的,即为其他函数添加特定功能的函数 装饰器的基本原则:

  1. 不能修改被装饰函数的源码
  2. 不能修改被装饰函数的调用方式

类中内置装饰器

ython 类中自带的装饰器是一些内置的、专门用于修改类行为的装饰器。这些装饰器可以帮助你更方便地实现特定的类行为或功能扩展。常见的内置类装饰器包括 @staticmethod@classmethod@property@abstractmethod 等。

@staticmethod

@staticmethod 装饰器用于将一个方法声明为静态方法。静态方法属于类本身,而不是类的实例。它不需要访问类实例或类本身,因此不需要传递 selfcls 参数。静态方法通常用于执行一些与类本身相关的任务,但不需要访问类的属性或实例数据。

1
2
3
4
5
6
7
8
class MyClass:
@staticmethod
def add(a, b):
return a + b

# 调用静态方法
result = MyClass.add(2, 3)
print(result) # 输出: 5

特点

  • 静态方法不依赖于类的实例化。
  • 可以通过类名或实例来调用静态方法,但通常通过类名调用。
  • 适用于那些不需要访问实例或类属性的方法。
@classmethod

@classmethod 装饰器用于将一个方法声明为类方法。类方法第一个参数是类本身(通常命名为 cls),而不是实例对象。类方法通常用于访问类属性或修改类状态,而不是实例状态。

1
2
3
4
5
6
7
8
9
10
class MyClass:
count = 0

@classmethod
def increment_count(cls):
cls.count += 1
print(f"Count: {cls.count}")

# 调用类方法
MyClass.increment_count() # 输出: Count: 1

特点

  • 类方法不需要类的实例,可以通过类本身直接调用。
  • 类方法第一个参数是类 cls,而不是实例 self
  • 类方法适用于操作类级别的数据或状态。
@property

@property 装饰器用于将一个方法定义为属性。这意味着你可以像访问属性一样访问这个方法,而无需显式调用它。@property 是一种将类的方法转换为只读属性的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Circle:
def __init__(self, radius):
self._radius = radius

@property
def radius(self):
return self._radius

@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative.")
self._radius = value

@property
def area(self):
return 3.14 * self._radius * self._radius

# 创建圆对象
circle = Circle(5)
print(circle.area) # 输出: 78.5
circle.radius = 10 # 使用setter修改半径
print(circle.area) # 输出: 314.0

特点

  • @property 使方法像属性一样访问。
  • 可以使用 @property@property.setter 来定义可读和可写的属性。
  • 用于将计算逻辑封装成属性访问的形式。
@abstractmethod

详见上文

魔法方法

重要的魔法方法

在 Python 中,魔法方法(Magic Methods)又称为 特殊方法(Special Methods),是类中以双下划线 __ 开头和结尾的方法。这些方法允许你对对象进行特定的操作或赋予对象特殊行为,从而让你可以定制 Python 对象的内建操作,比如加法、字符串表示、索引操作等。

魔法方法使得 Python 对象能够参与各种语言层面的操作,如算术运算、比较运算、类型转换、函数调用等,通常这些方法被 Python 内部调用,但你也可以自定义实现它们以修改对象行为。

__new__(cls)

  • 用于创建对象实例的方法。它通常与 __init__ 一起使用,在实例化对象时首先被调用。__new__ 返回一个新的对象实例,而 __init__ 则用来初始化该实例。
  • 当你继承 __new__ 方法时,你可以控制对象创建的过程。

__init__(self, ...)

  • 构造器,用于初始化对象实例。__init____new__ 方法之后调用,并且在对象实例创建后立即执行。
  • 它允许你在对象创建时设置初始属性。

__str__(self)

  • 用于定义对象的字符串表示。当使用 print() 打印对象或使用 str() 函数时,__str__ 方法被调用。
  • 这个方法应该返回一个友好的、可读性强的字符串,用于展示对象的内容。

__getitem__(self, key)

  • 用于定义对象的索引访问(例如 obj[key])。
    __setitem__(self, key, value)
  • 用于定义对象的索引赋值(例如 obj[key] = value)。
    __delitem__(self, key)
  • 用于定义删除索引元素(例如 del obj[key])。
1
2
3
4
5
6
7
8
9
class MyList:
def __init__(self, data):
self.data = data

def __getitem__(self, index):
return self.data[index]

my_list = MyList([1, 2, 3])
print(my_list[1]) # 输出: 2

__call__(self, ...)

  • 使对象成为可调用的(例如 obj())。当对象像函数一样被调用时,Python 会调用 obj.__call__() 方法。
1
2
3
4
5
6
7
8
9
class Adder:
def __init__(self, value):
self.value = value

def __call__(self, x):
return self.value + x

add_five = Adder(5)
print(add_five(3)) # 输出: 8
  • __enter__(self)
    • 用于定义进入 with 语句时的行为。
  • __exit__(self, exc_type, exc_val, exc_tb)
    • 用于定义退出 with 语句时的行为,处理异常等。
1
2
3
4
5
6
7
8
9
10
11
12
class MyContextManager:
def __enter__(self):
print("Entering the context")
return self

def __exit__(self, exc_type, exc_val, exc_tb):
print("Exiting the context")
if exc_type:
print(f"Exception: {exc_val}")

with MyContextManager():
print("Inside the context")

类的参数下划线前缀

单下划线 _ 前缀

单下划线是 Python 的一种约定,用来标识一个变量或方法是“内部使用的”,即它不应该被外部直接访问。这种标识并不影响属性或方法的访问权限,但它是对其他开发者的一种提醒,告诉他们这些属性或方法是属于类的实现细节,应该尽量避免直接使用。

双下划线 __ 前缀

双下划线前缀通常用于表示变量或方法是类的“私有”成员。它使得这些属性或方法不会被外部直接访问,而是通过名称修饰(name mangling)进行修改,使其在外部不可直接访问。这种命名约定旨在避免类的子类或外部代码意外覆盖或访问这些“私有”成员。

用途:

  • 强制私有化(name mangling)——Python 在名称前加上 _ClassName,使得外部不容易访问这些变量或方法。
  • 通过这种方式,Python 希望防止类的外部代码直接访问和修改这些变量,但仍然允许子类通过继承来访问和修改这些变量。
1
2
3
4
5
6
7
8
9
10
class MyClass:
def __init__(self):
self.__private_value = 100 # 私有属性

def __private_method(self):
print("This is a private method.")

obj = MyClass()
print(obj.__private_value) # 报错: AttributeError
obj.__private_method() # 报错: AttributeError