You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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())

遇到的问题

  1. 无论把password定义为实例变量(用类型注解)还是抽象属性,IDE都会标记password为未解析的属性引用
  2. 使用抽象属性方式时,运行时报错:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 02:25:27