掘金 后端 ( ) • 2024-03-08 13:42

Python具有语法清晰易读的优点,是一种广泛使用的高级编程语言。Python是为确保易用性而设计的,注重简洁性和降低程序的维护成本。它随带一个广泛的库,减少了开发人员从头开始编写代码的需要,并提高了开发人员的生产力。Python的一项有助于确保代码优雅的强大特性是装饰器(decorator)。

Python装饰器的定义

在Python中,decorator是一个函数,允许您修改另一个函数的行为,而不改变其核心逻辑。它接受另一个函数作为参数,并返回功能得到扩展的函数。这样一来,您就可以使用装饰器为现有函数添加一些额外的逻辑,只需要几行代码就可以提高可重用性。我们在本文中将介绍8个内置的Python装饰器,它们可以帮助您编写更优雅、更易于维护的代码。

1. @atexit.register

@atexit.register装饰器用于注册在程序结束时执行的函数。该函数可用于在程序即将退出时执行任何任务,无论是由于正常执行还是由于意外错误。

例子:

import atexit

# Register the exit_handler function
@atexit.register
def exit_handler():
 print("Exiting the program. Cleanup tasks can be performed here.")

# Rest of the program
def main():
 print("Inside the main function.")
 # Your program logic goes here.

if __name__ == "__main__":
 main()

输出:

Inside the main function.
Exiting the program. Cleanup tasks can be performed here.

在上面的实现中,@atexit.register在函数定义上面提及。它将exit_handler()函数定义为退出函数。实际上,这意味着每当程序到达其终止点,无论是通过正常执行还是由于意外错误导致过早退出,都将调用exit_handler()函数。

2. @dataclasses.dataclass

@dataclasses.dataclass是一个功能强大的装饰器,用于自动为“__init__”、“__repr__”等其他类生成常见的特殊方法。由于不需要编写用于初始化和比较类的实例的样板方法,它可以帮助您编写更干净、更简洁的代码。它还可以通过确保在整个代码库中一致地实现常见的特殊方法来帮助防止错误。

例子:

from dataclasses import dataclass

@dataclass
class Point:
 x: int
 y: int


point = Point(x=3, y=2)
# Printing object
print(point)

# Checking for the equality of two objects
point1 = Point(x=1, y=2)
point2 = Point(x=1, y=2)
print(point1 == point2)

输出:
Point(x=3, y=2)
True

@dataclass装饰器应用于Point类定义之上,通知Python使用默认行为来生成特殊方法。这会自动创建__init__方法,该方法在对象实例化时初始化类属性,比如x和y。因此,可以在不需要显式编码的情况下构造像point这样的实例。此外,负责提供对象字符串表示的__repr__方法也会自动加以调整。这确保了在打印输出对象(比如point)时,它会生成清晰而有序的表示,比如输出:point (x=3, y=2)中所示。

此外,两个实例point1和point2之间的相等性比较(==)生成True。这值得注意,因为默认情况下,Python根据内存位置来检查是否相等。然而,在数据类对象上下文中,相等性取决于其中含有的数据。这是由于@dataclass装饰器生成了一个__eq__方法,该方法检查对象中存在的数据是否相等,而不是检查相同的内存位置。

3. @enum.unique

@enum.unique装饰器位于enum模块中,用于确保枚举中所有成员的值是唯一的。这有助于防止意外创建具有相同值的多个枚举成员,不然会导致混淆和错误。如果发现重复的值,抛出ValueError(值错误)。

例子:

from enum import Enum, unique

@unique
class VehicleType(Enum):
 CAR = 1
 TRUCK = 2
 MOTORCYCLE = 3
 BUS = 4

# Attempting to create an enumeration with a duplicate value will raise a ValueError
try:
 @unique
 class DuplicateVehicleType(Enum):
 CAR = 1
 TRUCK = 2
 MOTORCYCLE = 3
 # BUS and MOTORCYCLE have duplicate values
 BUS = 3
except ValueError as e:
 print(f"Error: {e}")

输出:

Error: duplicate values found in : BUS -> MOTORCYCLE

在上述实现中,“BUS”和“MOTORCYCLE”有相同的值“3”。因此,@unique装饰器抛出值错误,显示一条消息,表明发现了重复的值。您既不能多次使用相同的键,也不能将相同的值分配给不同的成员。通过这种方式,它有助于防止多个枚举成员出现重复值。

4. @partial

partial装饰器是一个强大的工具,用于创建偏函数。偏函数允许您预先设置一些原始函数的参数,然后用已经填写的这些参数生成一个新的函数。

例子:

from functools import partial

# Original function
def power(base, exponent):
 return base ** exponent

# Creating a partial function with the exponent fixed to 2
square = partial(power, expnotallow=2)

# Using the partial function
result = square(3)
print("Output:",result)

输出:

Output: 9

在上面的实现中,我们有一个函数“power”,它接受两个参数“base”和“exponent”,并返回指数次幂基数的结果。我们使用原始函数创建了一个名为“square”的偏函数,其中指数被预先设置为2。这样一来,我们可以使用partial装饰器扩展原始函数的功能。

5. @singledispatch

@singledisptach装饰器用于创建泛型函数。它允许您定义名称相同但参数类型各异的函数的不同实现。当您希望代码针对不同的数据类型有不同的行为时,这个装饰器就特别有用。

例子:

from functools import singledispatch

# Decorator
@singledispatch
def display_info(arg):
 print(f"Generic: {arg}")

# Registering specialized implementations for different types
@display_info.register(int)
def display_int(arg):
 print(f"Received an integer: {arg}")

@display_info.register(float)
def display_float(arg):
 print(f"Received a float: {arg}")

@display_info.register(str)
def display_str(arg):
 print(f"Received a string: {arg}")

@display_info.register(list)
def display_sequence(arg):
 print(f"Received a sequence: {arg}")

# Using the generic function with different types
display_info(39) 
display_info(3.19) 
display_info("Hello World!")
display_info([2, 4, 6]) 

输出:

Received an integer: 39
Received a float: 3.19
Received a string: Hello World!
Received a sequence: [2, 4, 6]

在上面的实现中,我们先使用@singledisptach装饰器开发了泛型函数display_info(),然后分别为整数、浮点、字符串和列表注册了其实现。输出显示了display_info()针对不同数据类型的工作机理。

6. @classmethod

@classmethod是一个装饰器,用于在类中定义类方法。类方法绑定到类而不是绑定到类的对象。静态方法与类方法的主要区别在于它们与类状态的交互。类方法可以访问并修改类状态,而静态方法无法访问类状态、独立操作。

例子:

class Student:
 total_students = 0

 def __init__(self, name, age):
 self.name = name
 self.age = age
 Student.total_students += 1

 @classmethod
 def increment_total_students(cls):
 cls.total_students += 1
 print(f"Class method called. Total students now: {cls.total_students}")

# Creating instances of the class
student1 = Student(name="Tom", age=20)
student2 = Student(name="Cruise", age=22)

# Calling the class method
Student.increment_total_students() #Total students now: 3

# Accessing the class variable
print(f"Total students from student 1: {student1.total_students}")
print(f"Total students from student 2: {student2.total_students}")

输出:

Class method called. Total students now: 3
Total students from student 1: 3
Total students from student 2: 3

在上面的实现中,Student类拥有total_students这个类变量。@classmethod装饰器用于定义increment_total_students()类方法,以增加total_students变量。每当我们创建Student类的实例时,学生总数就增加1。我们创建了这个类的两个实例,然后使用类方法将total_students变量修改为3,这个类的实例也反映了这点。

7. @staticmethod

@staticmethod装饰器用于在类中定义静态方法。静态方法是无需创建类实例即可调用的方法。静态方法常常用于不需要访问与对象相关的参数,与整个类更加相关。

例子:

class MathOperations:
 @staticmethod
 def add(x, y):
 return x + y

 @staticmethod
 def subtract(x, y):
 return x - y

# Using the static methods without creating an instance of the class
sum_result = MathOperations.add(5, 4)
difference_result = MathOperations.subtract(8, 3)

print("Sum:", sum_result) 
print("Difference:", difference_result)

输出:

Sum: 9
Difference: 5

在上面的实现中,我们使用@staticmethod为类“MathOperations”定义了一个静态方法add()。我们添加了两个数字“4”和“5”,结果是“9”,没有创建类的任何实例。同样,将“8”和“3”两个数字相减得到“5”。这样一来,就可以生成静态方法,以执行不需要实例状态的效用函数。

8. @ property

@property装饰器用于定义类属性的getter方法。getter方法是返回属性值的方法。这种方法用于数据封装,指定谁可以访问类或实例的详细信息。

例子:

class Circle:
 def __init__(self, radius):
 self._radius = radius

 @property
 def radius(self):
 # Getter method for the radius.
 return self._radius

 @property
 def area(self):
 # Getter method for the area.
 return 3.14 * self._radius**2

# Creating an instance of the Circle class
my_circle = Circle(radius=5)

# Accessing properties using the @property decorator
print("Radius:", my_circle.radius) 
print("Area:", my_circle.area) 

输出:

Radius: 5
Area: 78.5

在上面的实现中,类“Circle”有一个属性“radius”。我们使用@property为半径和面积设置了getter方法。它为类的用户提供了一个干净一致的接口来访问这些属性。

结语

本文重点介绍了一些最通用和最实用的装饰器,您可以使用它们提高代码的灵活性和可读性。这些修饰器让您可以扩展原始函数的功能,使原始函数更富条理性,更不容易出错。它们如同神奇的魔法棒,使您的Python程序看起来整洁、运行起来顺畅。