装饰器与类属性、方法的那些事儿
2023-12-13 11:11:33
前言
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
是类属性方法,它通过一个方法计算类的实例的name
和age
属性的值。
区别
两种方式定义的方法的主要区别在于cls
和self
。cls
是类方法的第一个参数,它代表类本身。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()
是一个箭头函数,它试图访问类的实例属性name
和age
。但是,由于箭头函数没有自己的this
,因此它无法访问类的实例属性name
和age
。
为了解决这个问题,我们需要将类的实例作为参数传递给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()
方法就可以访问类的实例属性name
和age
了。
总结
本文介绍了class中两种方法定义方式之间的区别、decorator如何作用于class的方法、如何实现一个兼容class普通方法和class属性方法的装饰器,以及如何保留装饰器装饰的箭头函数式中this为类实例的特性。