Python:能否像创建计算实例属性一样实现计算类属性?
我正在开发一个大型Python pygame双人在线多线程游戏,已经体会到计算属性的便利性。现在遇到一个问题:游戏里有一个供两名玩家共享的card_group,目前它是计算实例属性,更新时会执行x、y、z操作。我需要将其改为类属性,让两名玩家/客户端(各自独立连接)能统一发送/接收/更新这一个类属性,而非两个P1/P2实例属性。由于游戏复杂、代码量达数千行,我更倾向于使用单个变量,这样更合理。
但我无法成功将类属性转为计算属性,本以为这符合Python风格且简单,却不知如何实现。
注:我不想使用方法,除非万不得已,更希望它保持为属性/特性。
我在测试文件中编写了模拟代码,但最多只能返回一个property对象。我有三个问题:
- 能否从这个property对象中获取值?
- 能否使用@property装饰器或property()方法让类属性像计算实例属性一样工作?
- 有没有其他方法实现计算类属性?
测试代码如下:
class Game: _card_group = 0 @property def card_group(self): return self._card_group @card_group.setter def card_group(self, val): print('Setter') self._card_group = val Game.card_group = 3 print(Game.card_group)
在上述示例中,执行Game.card_group = 3时并未如预期调用card_group.setter,且打印Game.card_group时输出的是property对象。
问题1:能否从这个property对象中获取值?
能,但你的写法有误。你定义的card_group是实例属性的property,当直接访问Game.card_group时,拿到的是类中存储的property对象本身,而非它包装的值。如果要通过这个property获取值,必须通过类的实例调用(比如Game().card_group),但这会访问实例的_card_group,而非类级别的共享变量,不符合你的需求。
问题2:能否使用@property装饰器或property()方法让类属性像计算实例属性一样工作?
直接用@property装饰器不行——它默认是为实例属性设计的。但可以通过元类或者手动创建类级别的property对象来实现,让类属性拥有和实例计算属性一样的getter/setter逻辑。
问题3:有没有其他方法实现计算类属性?
有两种常用且简洁的方法:
方法一:元类实现类计算属性
元类是类的类,通过在元类中定义@property,可以让所有继承该元类的类拥有类级别的计算属性:
class GameMeta(type): _card_group = 0 @property def card_group(cls): return cls._card_group @card_group.setter def card_group(cls, val): print('类Setter被调用') # 这里可以添加你需要执行的x、y、z操作 cls._card_group = val class Game(metaclass=GameMeta): pass # 测试 Game.card_group = 3 print(Game.card_group) # 输出3,同时打印"类Setter被调用"
方法二:手动创建类级别的property对象
直接在类上绑定property,搭配类方法作为getter和setter:
class Game: _card_group = 0 @classmethod def _get_card_group(cls): return cls._card_group @classmethod def _set_card_group(cls, val): print('类Setter被调用') # 插入x、y、z操作逻辑 cls._card_group = val # 手动创建类属性的property,注意要取类方法的__func__属性 card_group = property(_get_card_group.__func__, _set_card_group.__func__) # 测试 Game.card_group = 3 print(Game.card_group) # 输出3,同时打印"类Setter被调用"
为什么你的测试代码不生效?
你原来的@property是为实例属性定义的,当你赋值Game.card_group = 3时,是直接覆盖了类中存储的property对象,而非触发setter逻辑。而打印Game.card_group时,自然输出的是被覆盖前的property对象本身。
内容的提问来源于stack exchange,提问作者Joshua Merrifield

