Python 的类装饰器
今天就来聊一个不少面试会考,但很多人平时压根不咋用的技术点:Python 的类装饰器(Class Decorators)。
说实话,装饰器这个东西,最早我看到的时候是有点懵的,尤其是 @property、@classmethod、@staticmethod 这些用在类里的,看着都差不多,一不小心就混了。
但如果你真理解了它们在类中扮演的角色,那你会发现——嘿,写代码变得优雅了,逻辑也清爽了不少。
装饰器:property 上线!
我第一次真香 @property,是写个图形类的时候。需求是这样的:有一个 Circle 类,必须保证半径不能为负数,偏偏这个 radius 还得支持动态赋值。
正常人的写法是这么写的:
classCircle:
def__init__(self, radius):
if radius < 0:
raise ValueError("Radius must be >= 0")
self._radius = radius
defget_radius(self):
return self._radius
defset_radius(self, value):
if value < 0:
raise ValueError("Radius must be >= 0")
self._radius = value
然后使用的时候:
c = Circle(5)
print(c.get_radius())
c.set_radius(-2) # 啪,爆炸
但你看这用法,get/set 跟 Java 一模一样,写 Python 还这么用,不累吗?这时候 @property 一出手,优雅值直接拉满。
classCircle:
def__init__(self, radius: float) -> None:
self._radius = radius
@property
defradius(self) -> float:
return self._radius
@radius.setter
defradius(self, value: float) -> None:
if value < 0:
raise ValueError('Radius must be >= 0!')
self._radius = value
然后使用方式简直太舒服:
c = Circle(3)
c.radius = 5# 自动调用 setter
print(c.radius) # 自动调用 getter
这时候你会发现,“getter/setter”三个字不见了,但控制力一点没少,还更 Pythonic。而且还自带封装性,变量前加了下划线 _radius,外部直接访问不了。
我觉得这才是 Python 设计优雅的地方,写起来不费力,用起来像变量,但背后逻辑依然强大。
静态方法 staticmethod:就你最独立
接下来是 @staticmethod。如果你在类里面写一个方法,它既不操作实例属性(没有用 self),也不操作类属性(没有用 cls),那它干嘛放类里面呢?
听着好像多余,其实就是为了代码组织性。比如我写一个 Circle 类,我想加一个方法,告诉大家“圆形”用英文怎么说,但这个跟对象本身一毛钱关系都没有。
classCircle:
@staticmethod
defshape_string() -> str:
return'circle'
然后你就可以:
print(Circle.shape_string()) # 输出 'circle'
注意哦,这不是实例方法,不需要 Circle()。你说直接写一个模块函数也行啊,对,没毛病,但放类里逻辑更聚合,对读代码的人更友好,这是架构感的一部分。
类方法 classmethod:这个家族我罩的
接下来是我最爱用的 @classmethod,这个东西我真的觉得被低估了太多。尤其是你写一些“辅助构造函数”的时候,简直神器。
举个例子,你写一个 Circle 类,想根据圆的面积来创建一个对象,那就得从面积反推半径。
import math
classCircle:
def__init__(self, radius: float) -> None:
self._radius = radius
@classmethod
deffrom_area(cls, area: float) -> "Circle":
radius = math.sqrt(area / math.pi)
return cls(radius)
使用方式非常丝滑:
c = Circle.from_area(math.pi)
print(c.radius) # 输出 1.0
这个 cls 就是当前类,它不像 self 指向实例,它指向类本身,这样就算你后续子类化了,也可以返回子类对象,非常适合写“多形态的构造器”。
我觉得只要你写代码时,发现 __init__ 参数不够表达需求了,就该考虑是不是可以加个 @classmethod 构造器。
面试考察点
面试的时候,遇到这种你能加但不一定加的功能点,就是体现你“代码设计意识”的好机会。
我遇到过一个题:实现一个图形类,有圆、有矩形,有公共的接口,还要支持扩展。我一开始写了个基类,子类去继承。
后来我直接加了个 @classmethod def from_string(cls, s: str),输入是 "circle:5",就能解析成对象。
面试官一听眼睛亮了:“你还会设计构造器工厂啊?”我笑着说:“还能加缓存呢,要不要我顺便上个 @lru_cache?”
有时候你主动带出一个点,哪怕不是关键点,面试官都会记得你。
你以为只有这些装饰器?
当然不是了,@property、@staticmethod、@classmethod 是“类装饰器三兄弟”,但你完全可以写自己的装饰器,或者用现成的,比如:
functools.lru_cache:让函数支持缓存,特别适合递归计算。contextlib.contextmanager:写上下文管理器不用写类。@dataclass:Python 3.7 以后有了这个神器,数据类代码量省一半。
你要是能在项目里用得顺手,面试讲出来,绝对加分。
面试题最优解
题目:设计一个 Rectangle 类,要求支持:
设置长宽 面积属性只读 提供一个 from_string("3x4")方法生成对象
最优解参考代码:
classRectangle:
def__init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
@property
defarea(self) -> float:
return self.width * self.height
@classmethod
deffrom_string(cls, s: str) -> "Rectangle":
parts = s.lower().split('x')
if len(parts) != 2:
raise ValueError("Invalid format, expected 'WxH'")
width, height = map(float, parts)
return cls(width, height)
使用方式:
r = Rectangle.from_string("3x4")
print(r.area) # 输出 12.0
这个答案的关键点是用到了 @property 做只读面积,@classmethod 做构造辅助,非常贴合面试的考察目标。
也可以看我写的这篇文章《DeepSeek满血复活,直接起飞!》来进行本地搭建。
对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》。