抽象属性(不是属性)?

Lap*_*not 56 python abstract

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

我想写一些类似的东西:

class AbstractFoo(metaclass=ABCMeta):

    @property
    @abstractmethod
    def bar(self):
        pass

class Foo(AbstractFoo):

    def __init__(self):
        self.bar = 3
Run Code Online (Sandbox Code Playgroud)

代替:

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
Run Code Online (Sandbox Code Playgroud)

属性很方便,但对于不需要计算的简单属性,它们是一种过度杀伤.这对于将由用户进行子类化和实现的抽象类尤其重要(我不想强迫某人@property在他刚写入self.foo = foo时使用__init__).

Python问题中的抽象属性建议仅作为使用的答案,@property并且@abstractmethod:它不回答我的问题.

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

Séb*_*rez 25

如果您确实希望强制子类定义给定属性,则可以使用metaclass.就个人而言,我认为这可能是矫枉过正,而不是非常pythonic,但你可以做这样的事情:

 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
Run Code Online (Sandbox Code Playgroud)

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

GoodFoo()  # ok
BadFoo ()  # yield NotImplementedError
Run Code Online (Sandbox Code Playgroud)


kra*_*ski 24

它是2018年,我们应该得到更好的解决方案:

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
Run Code Online (Sandbox Code Playgroud)

它会表现得像这样:

Foo()     # ok
BadFoo()  # will raise: NotImplementedError: Can't instantiate abstract class BadFoo
# with abstract attributes: bar
Run Code Online (Sandbox Code Playgroud)

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

这是better_abc.py内容:

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
Run Code Online (Sandbox Code Playgroud)

好的是你可以这样做:

class AbstractFoo(metaclass=ABCMeta):
    bar = abstract_attribute()
Run Code Online (Sandbox Code Playgroud)

它将与上面相同.

也可以使用:

class ABC(ABCMeta):
    pass
Run Code Online (Sandbox Code Playgroud)

定义自定义ABC帮助器.PS.我认为此代码为CC0.

这可以通过使用AST解析器通过扫描__init__代码来提前(在类声明上)来改进,但现在似乎是一种矫枉过正(除非有人愿意实现).

  • 我希望我能多点赞。希望这个解决方案能在某个时候集成到标准库中。谢谢你。 (4认同)
  • 如果其他人对这个优雅的解决方案破坏了 pylint 类型提示感到沮丧,您可以更进一步,将 `bar = Abstract_attribute()` 设置为 `bar: int = Abstract_attribute()`。可能是常识,但我花了几个小时才意识到这一点。 (3认同)
  • 我已经打包了这个代码片段:`pip install better-abc`。如果您想贡献一些测试或其他功能,请在[此处](https://github.com/shonin/better-abc)打开拉取请求 (2认同)
  • 这是一个很好的评论@SMiller,谢谢!我用打字支持所需的草稿更新了答案(它可能无法涵盖所有​​边缘情况,但我希望它有所帮助)。 (2认同)

Ane*_*pic 14

仅仅因为你abstractproperty在抽象基类上定义它并不意味着你必须在子类上创建一个属性.

你可以:

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>
Run Code Online (Sandbox Code Playgroud)

如果要初始化值,__init__可以执行以下操作:

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>
Run Code Online (Sandbox Code Playgroud)


Vee*_*rac 7

问题不是什么,而是什么时候:

from abc import ABCMeta, abstractmethod

class AbstractFoo(metaclass=ABCMeta):
    @abstractmethod
    def bar():
        pass

class Foo(AbstractFoo):
    bar = object()

isinstance(Foo(), AbstractFoo)
#>>> True
Run Code Online (Sandbox Code Playgroud)

bar不是一种方法并不重要!问题在于__subclasshook__,执行检查的方法是a classmethod,因此只关心而不是实例是否具有该属性.


我建议你不要强迫这个,因为这是一个难题.另一种方法是强制它们预定义属性,但这只会留下虚拟属性,只会使错误无效.


小智 6

正如Anentropic所说,您不必将 an 实现abstractproperty为 another property

然而,所有答案似乎都忽略的一件事是 Python 的成员槽(__slots__类属性)。__slots__如果需要的只是数据属性,那么需要实现抽象属性的 ABC 的用户可以简单地在其中定义它们。

所以用类似的东西,

class AbstractFoo(abc.ABC):
    __slots__ = ()

    bar = abc.abstractproperty()
Run Code Online (Sandbox Code Playgroud)

用户可以简单地定义子类,

class Foo(AbstractFoo):
    __slots__ = 'bar',  # the only requirement

    # define Foo as desired

    def __init__(self):
        self.bar = ...
Run Code Online (Sandbox Code Playgroud)

在这里,它的Foo.bar行为就像一个普通的实例属性,只是实现方式不同。这很简单、高效,并且避免了@property您描述的样板文件。

无论 ABC 是否__slots__在其类的主体中定义,这都有效。然而,__slots__一路走下去不仅可以节省内存并提供更快的属性访问,而且还提供了一个有意义的描述符,而不是bar = None在子类中具有中间体(例如或类似的)。1

一些答案建议实例化(即在元类__call__()方法中)进行“抽象”属性检查但我发现这不仅浪费而且可能效率低下,因为初始化步骤可能是一个耗时的步骤。

简而言之,ABC 的子类所需要的是覆盖相关的描述符(无论是属性还是方法),无论如何,并向您的用户记录可以__slots__用作抽象属性的实现似乎对我来说是更合适的方法。


1 在任何情况下,至少,ABC 应该始终定义一个空的__slots__类属性,否则子类在实例化时被迫具有__dict__(动态属性访问)和__weakref__(弱引用支持)。有关标准库中这种情况的示例,请参阅abccollections.abc模块。

  • 当 abc.abstractproperty 被弃用时,这如何成立? (2认同)