返回

装饰器与类属性、方法的那些事儿

前端

前言

Python的类中提供两种方法来定义方法:一种是类方法,另一种是类属性方法。两种方式定义的方法用途各不相同,类方法强调与类的关系,而类属性方法则强调与类实例的关系。此外,Python为开发者提供了@decorator这样的语法糖,方便开发者对函数进行修饰。然而,如果需要对类的方法进行修饰,那么究竟如何处理呢?

两种方法

在介绍如何对class方法进行修饰之前,我们先来看一下class中两种方法定义方式之间的区别。

类方法

类方法通过@classmethod修饰器定义,以cls作为第一个参数。类方法的作用是访问类的属性和方法,修改类的属性,以及创建类的实例。

举个例子:

class Person:
    species = "Homo sapiens"

    @classmethod
    def get_species(cls):
        return cls.species

    @classmethod
    def create_person(cls, name, age):
        return cls(name, age)

p1 = Person.create_person("John", 30)

在这个例子中,get_species()是类方法,它用于获取类的species属性。create_person()也是类方法,它用于创建类的实例。

类属性方法

类属性方法通过@property修饰器定义。类属性方法可以像普通属性一样访问,但是它的值是通过一个方法计算得到的。

举个例子:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @property
    def full_name(self):
        return f"{self.name} {self.age}"

p1 = Person("John", 30)
print(p1.full_name)  # John 30

在这个例子中,full_name是类属性方法,它通过一个方法计算类的实例的nameage属性的值。

区别

两种方式定义的方法的主要区别在于clsselfcls是类方法的第一个参数,它代表类本身。self是类属性方法的第一个参数,它代表类的实例。

装饰器

装饰器是一种Python语法糖,它允许开发者在不修改函数源代码的情况下修改函数的行为。装饰器通过@decorator修饰器语法应用于函数。

举个例子:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before calling function")
        result = func(*args, **kwargs)
        print("After calling function")
        return result
    return wrapper

@my_decorator
def my_function(x, y):
    return x + y

print(my_function(1, 2))  # Before calling function
                             # 3
                             # After calling function

在这个例子中,my_decorator()是一个装饰器,它将my_function()函数包装在一个wrapper()函数中。当调用my_function()时,实际上是调用了wrapper()函数。wrapper()函数在调用my_function()函数之前和之后打印一些信息。

修饰类方法

现在,我们知道如何定义类方法和装饰器了。那么,如何对class方法进行修饰呢?

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Before calling function")
        result = func(*args, **kwargs)
        print("After calling function")
        return result
    return wrapper

class Person:
    species = "Homo sapiens"

    @my_decorator
    @classmethod
    def get_species(cls):
        return cls.species

    @my_decorator
    @classmethod
    def create_person(cls, name, age):
        return cls(name, age)

p1 = Person.create_person("John", 30)

在这个例子中,my_decorator()装饰器被应用于get_species()create_person()类方法。当调用get_species()create_person()方法时,实际上是调用了wrapper()函数。wrapper()函数在调用get_species()create_person()方法之前和之后打印一些信息。

兼容class普通方法和class属性方法的装饰器

前面的例子中,我们对class方法进行了修饰。但是,我们还没有对class属性方法进行修饰。那么,如何实现一个兼容class普通方法和class属性方法的装饰器呢?

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        print("Before calling function")
        result = func(self, *args, **kwargs)
        print("After calling function")
        return result
    return wrapper

class Person:
    species = "Homo sapiens"

    @my_decorator
    def get_name(self):
        return self.name

    @my_decorator
    @property
    def full_name(self):
        return f"{self.name} {self.age}"

p1 = Person("John", 30)

print(p1.get_name())  # Before calling function
                       # John
                       # After calling function

print(p1.full_name)  # Before calling function
                      # John 30
                      # After calling function

在这个例子中,my_decorator()装饰器被应用于get_name()类方法和full_name类属性方法。当调用get_name()方法和full_name属性时,实际上是调用了wrapper()函数。wrapper()函数在调用get_name()方法和full_name属性之前和之后打印一些信息。

值得注意的是,在对class属性方法进行修饰时,我们需要将self作为第一个参数传递给wrapper()函数。这是因为class属性方法是通过一个方法计算类的实例的属性的值的。

保留箭头函数式中this为类实例的特性

在ES6中,箭头函数是没有自己的this的,箭头函数的this是其外层函数的this。如果我们需要在箭头函数中访问类的实例属性或方法,那么我们需要将类的实例作为参数传递给箭头函数。

举个例子:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  getFullName() {
    return `${this.name} ${this.age}`;
  }
}

const p1 = new Person("John", 30);

const getFullNameArrow = () => {
  return this.getFullName();
};

console.log(getFullNameArrow());  // Uncaught TypeError: Cannot read properties of undefined (reading 'getFullName')

在这个例子中,getFullNameArrow()是一个箭头函数,它试图访问类的实例属性nameage。但是,由于箭头函数没有自己的this,因此它无法访问类的实例属性nameage

为了解决这个问题,我们需要将类的实例作为参数传递给getFullNameArrow()函数。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  getFullName() {
    return `${this.name} ${this.age}`;
  }
}

const p1 = new Person("John", 30);

const getFullNameArrow = (person) => {
  return person.getFullName();
};

console.log(getFullNameArrow(p1));  // John 30

在这个例子中,getFullNameArrow()函数接受一个参数person,并将person作为this传递给getFullName()方法。这样,getFullName()方法就可以访问类的实例属性nameage了。

总结

本文介绍了class中两种方法定义方式之间的区别、decorator如何作用于class的方法、如何实现一个兼容class普通方法和class属性方法的装饰器,以及如何保留装饰器装饰的箭头函数式中this为类实例的特性。