首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >抽象属性(不是属性)?

抽象属性(不是属性)?
EN

Stack Overflow用户
提问于 2014-05-23 14:12:21
回答 6查看 35.1K关注 0票数 110

定义抽象实例属性(而不是属性)的最佳实践是什么?

我想写这样的东西:

代码语言:javascript
复制
class AbstractFoo(metaclass=ABCMeta):

    @property
    @abstractmethod
    def bar(self):
        pass

class Foo(AbstractFoo):

    def __init__(self):
        self.bar = 3

而不是:

代码语言:javascript
复制
class Foo(AbstractFoo):

    def __init__(self):
        self._bar = 3

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def setbar(self, bar):
        self._bar = bar

    @bar.deleter
    def delbar(self):
        del self._bar

属性很方便,但是对于不需要计算的简单属性,它们是过头的。这对于将由用户子类和实现的抽象类尤其重要(我不想强迫某个人使用@property,因为他本来可以在__init__中编写self.foo = foo )。

Python中的抽象属性问题是使用@property@abstractmethod的唯一答案:它没有回答我的问题。

通过ActiveState实现抽象类属性的AbstractAttribute方法可能是正确的,但我不确定。它也只适用于类属性,而不适用于实例属性。

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2014-05-23 15:24:54

如果您确实希望强制子类定义给定的属性,则可以使用元类:

代码语言:javascript
复制
 class AbstractFooMeta(type):
 
     def __call__(cls, *args, **kwargs):
         """Called when you call Foo(*args, **kwargs) """
         obj = type.__call__(cls, *args, **kwargs)
         obj.check_bar()
         return obj
     
     
 class AbstractFoo(object):
     __metaclass__ = AbstractFooMeta
     bar = None
 
     def check_bar(self):
         if self.bar is None:
             raise NotImplementedError('Subclasses must define bar')
 
 
 class GoodFoo(AbstractFoo):
     def __init__(self):
         self.bar = 3
 
 
 class BadFoo(AbstractFoo):
     def __init__(self):
         pass

基本上,元类重新定义__call__,以确保在实例上的init之后调用check_bar

代码语言:javascript
复制
GoodFoo()  # ok
BadFoo ()  # yield NotImplementedError
票数 32
EN

Stack Overflow用户

发布于 2018-05-16 22:57:35

与公认的答案相比,这可能是一个更好的解决方案:

代码语言:javascript
复制
from better_abc import ABCMeta, abstract_attribute    # see below

class AbstractFoo(metaclass=ABCMeta):

    @abstract_attribute
    def bar(self):
        pass

class Foo(AbstractFoo):
    def __init__(self):
        self.bar = 3

class BadFoo(AbstractFoo):
    def __init__(self):
        pass

它的行为会是这样:

代码语言:javascript
复制
Foo()     # ok
BadFoo()  # will raise: NotImplementedError: Can't instantiate abstract class BadFoo
# with abstract attributes: bar

这个答案使用了与接受的答案相同的方法,但是与内置的ABC很好地集成,并且不需要check_bar()助手的样板。

以下是better_abc.py内容:

代码语言:javascript
复制
from abc import ABCMeta as NativeABCMeta

class DummyAttribute:
    pass

def abstract_attribute(obj=None):
    if obj is None:
        obj = DummyAttribute()
    obj.__is_abstract_attribute__ = True
    return obj


class ABCMeta(NativeABCMeta):

    def __call__(cls, *args, **kwargs):
        instance = NativeABCMeta.__call__(cls, *args, **kwargs)
        abstract_attributes = {
            name
            for name in dir(instance)
            if getattr(getattr(instance, name), '__is_abstract_attribute__', False)
        }
        if abstract_attributes:
            raise NotImplementedError(
                "Can't instantiate abstract class {} with"
                " abstract attributes: {}".format(
                    cls.__name__,
                    ', '.join(abstract_attributes)
                )
            )
        return instance

好的是你能做到:

代码语言:javascript
复制
class AbstractFoo(metaclass=ABCMeta):
    bar = abstract_attribute()

它的工作原理和上面一样。

还可以使用:

代码语言:javascript
复制
class ABC(ABCMeta):
    pass

若要定义自定义ABC助手,请执行以下操作。PS。我认为这个代码是CC0。

这可以通过使用AST解析器来通过扫描__init__代码来更早地(在类声明上)来改进,但目前看来这似乎是过分的(除非有人愿意实现)。

2021:打字支助

您可以使用:

代码语言:javascript
复制
from typing import cast, Any, Callable, TypeVar


R = TypeVar('R')


def abstract_attribute(obj: Callable[[Any], R] = None) -> R:
    _obj = cast(Any, obj)
    if obj is None:
        _obj = DummyAttribute()
    _obj.__is_abstract_attribute__ = True
    return cast(R, _obj)

这将让我突出一些打字问题。

代码语言:javascript
复制
class AbstractFooTyped(metaclass=ABCMeta):

    @abstract_attribute
    def bar(self) -> int:
        pass


class FooTyped(AbstractFooTyped):
    def __init__(self):
        # skipping assignment (which is required!) to demonstrate
        # that it works independent of when the assignment is made
        pass


f_typed = FooTyped()
_ = f_typed.bar + 'test'   # Mypy: Unsupported operand types for + ("int" and "str")


FooTyped.bar = 'test'    # Mypy: Incompatible types in assignment (expression has type "str", variable has type "int")
FooTyped.bar + 'test'    # Mypy: Unsupported operand types for + ("int" and "str")

对于缩写符号,如@SMiller在评论中所建议的:

代码语言:javascript
复制
class AbstractFooTypedShorthand(metaclass=ABCMeta):
    bar: int = abstract_attribute()


AbstractFooTypedShorthand.bar += 'test'   # Mypy: Unsupported operand types for + ("int" and "str")
票数 73
EN

Stack Overflow用户

发布于 2017-03-01 10:52:23

仅仅因为在抽象基类上将其定义为abstractproperty并不意味着您必须在子类上创建一个属性。

例如,你可以:

代码语言:javascript
复制
In [1]: from abc import ABCMeta, abstractproperty

In [2]: class X(metaclass=ABCMeta):
   ...:     @abstractproperty
   ...:     def required(self):
   ...:         raise NotImplementedError
   ...:

In [3]: class Y(X):
   ...:     required = True
   ...:

In [4]: Y()
Out[4]: <__main__.Y at 0x10ae0d390>

如果要在__init__中初始化值,可以这样做:

代码语言:javascript
复制
In [5]: class Z(X):
   ...:     required = None
   ...:     def __init__(self, value):
   ...:         self.required = value
   ...:

In [6]: Z(value=3)
Out[6]: <__main__.Z at 0x10ae15a20>

因为Python3.3中的abstractproperty已弃用。因此Python 3用户应该使用以下内容:

代码语言:javascript
复制
from abc import ABCMeta, abstractmethod

class X(metaclass=ABCMeta):
    @property
    @abstractmethod
    def required(self):
        raise NotImplementedError
票数 32
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23831510

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档