Python抽象基类中属性的正确定义方法及运行时包装问题
问题:Python抽象基类中定义抽象属性的正确方式
背景与代码实现
我希望创建一个抽象基类来定义共享属性和方法,编写了如下抽象类:
from abc import ABCMeta, abstractmethod class Person(metaclass=ABCMeta): # 这种方式无效 # password: str def __init__(self): pass @abstractmethod def getRoles(self) -> list: pass # 这种方式也不行 @abstractmethod @property def password(self): pass @abstractmethod @password.setter def password(self, password): pass
随后编写了子类User:
from src.Person import Person class User(Person): def __init__(self, username: str = "blank", password: str =""): super(User, self).__init__() self.roles = ["System User"] self.username = username self.password = password def getRoles(self): return self.roles
目前子类运行正常,但我需要一个能在运行时扩展任意Person类的包装函数,于是编写了如下代码:
from src.Person import Person def person_wrapper(person: Person): class MyWrapper(person): def __init__(self, mask: bool, extra_roles: list): super(MyWrapper, self).__init__() self.mask = mask self.extra_roles = extra_roles def getRoles(self) -> list: return self.getRoles() + self.extra_roles def getPassword(self) -> str: if self.mask: return self.password.encode("ascii") return MyWrapper
主函数调用如下:
if __name__ == '__main__': base_env: Person = get_environment("user") env = person_wrapper(base_env)( True, ["abc", "123"]) print(env.getRoles()) print(env.getPassword())
遇到的问题
- 无论把
password定义为实例变量(用类型注解)还是抽象属性,IDE都会标记password为未解析的属性引用 - 使用抽象属性方式时,运行时报错:
AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable
解决方案:Python定义抽象属性的正确方式
1. 修正抽象基类的属性定义
Python中无法直接定义抽象实例变量,但可以通过**@property + @abstractmethod组合实现抽象属性,核心是装饰器顺序必须正确**:先应用@property,再应用@abstractmethod;setter同理,先写@属性.setter,再写@abstractmethod。
修正后的Person类:
from abc import ABCMeta, abstractmethod class Person(metaclass=ABCMeta): def __init__(self): pass @abstractmethod def getRoles(self) -> list: pass # 正确顺序:先@property,再@abstractmethod @property @abstractmethod def password(self): pass # setter正确顺序:先@password.setter,再@abstractmethod @password.setter @abstractmethod def password(self, password): pass
2. 子类正确实现抽象属性
子类必须实现抽象属性的getter和setter,不能直接用实例变量赋值(抽象基类会强制要求实现抽象成员),可以用私有变量存储实际值:
修正后的User类:
from src.Person import Person class User(Person): def __init__(self, username: str = "blank", password: str =""): super(User, self).__init__() self.roles = ["System User"] self.username = username self._password = password # 用私有变量存储密码 def getRoles(self): return self.roles # 实现抽象getter @property def password(self): return self._password # 实现抽象setter @password.setter def password(self, password): self._password = password
3. 修复包装函数的错误
包装函数存在两个核心问题:参数类型错误(应该传入类而非实例)、getRoles递归调用自身。修正后的代码:
from src.Person import Person from typing import Type def person_wrapper(person_cls: Type[Person]): # 参数改为类类型,而非实例 class MyWrapper(person_cls): def __init__(self, mask: bool, extra_roles: list, *args, **kwargs): # 传递父类初始化所需的参数 super(MyWrapper, self).__init__(*args, **kwargs) self.mask = mask self.extra_roles = extra_roles def getRoles(self) -> list: # 调用父类的getRoles,避免无限递归 return super().getRoles() + self.extra_roles def getPassword(self) -> str: if self.mask: return self.password.encode("ascii").decode("ascii") # 按需处理字节转字符串 return self.password return MyWrapper
4. 修正主函数调用
确保传入包装函数的是Person子类的类对象,而非实例,并传递父类初始化所需参数:
if __name__ == '__main__': # 假设get_environment返回User类,若返回实例需调整逻辑 base_env_cls = get_environment("user") # 传入mask、extra_roles,以及User初始化需要的username和password env = person_wrapper(base_env_cls)(True, ["abc", "123"], username="test_user", password="123456") print(env.getRoles()) print(env.getPassword())
关键说明
- 抽象属性的装饰器顺序是核心:必须先
@property再@abstractmethod,否则会触发__isabstractmethod__不可写的错误 - 子类必须实现所有抽象成员(包括抽象方法和抽象属性的getter/setter),否则无法实例化
- 包装函数需要接收类而非实例,否则会尝试继承实例而非类,引发逻辑错误
内容的提问来源于stack exchange,提问作者pbuchheit
相关产品推荐
相关产品推荐

