2014-05-23 152 views
23

定义抽象实例属性的最佳做法是什么,但不是作为属性定义的?抽象属性(非属性)?

我想写点东西像:

class AbstractFoo(metaclass=ABCMeta): 

    @property 
    @abstractmethod 
    def bar(self): 
     pass 

class Foo(AbstractFoo): 

    def __init__(self): 
     self.bar = 3 

相反的:

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

Abstract attributes in Python问题建议只回答使用@property@abstractmethod:它不回答我的问题。

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

+0

为什么你需要强迫某人在他们的班上有特定的属性吗? –

+7

是不是ABC的整个事情?如果你想要我的具体例子,我想让人们为他们的传感器写一个类,并且该类应该有一个self.port属性。 – Lapinot

+0

经过反思,是的,我想这是;虽然我认为这是相当苍蝇在ducktyping ... –

回答

13

如果您确实想要强制子类定义给定的属性,则可以使用元类。就个人而言,我认为这可能是矫枉过正,不是很Python的,但你可以做这样的事情:

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__确保check_bar上实例的初始化后调用。

GoodFoo()  # ok 
BadFoo()  # yield NotImplementedError 
+0

我同意,这可能是一个矫枉过正!但是这个解决方案很有趣...... – Lapinot

5

的问题不是什么,但

​​

这不要紧,bar不是方法!问题是,__subclasshook__,做检查的方法,是一种classmethod,所以只关心的,而不是实例是否具有属性。


我建议你不要强迫这个,因为这是一个难题。另一种方法是强制他们预先定义属性,但这只会让虚拟属性留下一些沉默错误。

+0

是的,这是我从现在开始做的事情(但将其定义为抽象**方法**很奇怪)。但是它并没有解决实例属性的问题。 – Lapinot

+0

我认为[this](http://stackoverflow.com/a/32536493/2943985)是一个更好的(更pythonic)解决方案。 –

0

我在周围搜索了一段时间,但没有看到任何我喜欢的东西。正如你可能知道,如果你这样做:

class AbstractFoo(object): 
    @property 
    def bar(self): 
     raise NotImplementedError(
       "Subclasses of AbstractFoo must set an instance attribute " 
       "self._bar in it's __init__ method") 

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

f = Foo() 

你得到一个AttributeError: can't set attribute这是烦人。

要解决这个问题,你可以这样做:

class AbstractFoo(object): 

    @property 
    def bar(self): 
     try: 
      return self._bar 
     except AttributeError: 
      raise NotImplementedError(
       "Subclasses of AbstractFoo must set an instance attribute " 
       "self._bar in it's __init__ method") 

class OkFoo(AbstractFoo): 
    def __init__(self): 
     self._bar = 3 

class BadFoo(AbstractFoo): 
    pass 

a = OkFoo() 
b = BadFoo() 
print a.bar 
print b.bar # raises a NotImplementedError 

这就避免了AttributeError: can't set attribute但如果你只是离开过抽象属性都在一起:

class AbstractFoo(object): 
    pass 

class Foo(AbstractFoo): 
    pass 

f = Foo() 
f.bar 

你得到一个AttributeError: 'Foo' object has no attribute 'bar'这可以说是几乎与NotImplementedError一样好。所以真的我的解决方案是从另一个交易错误信息..你必须使用self._bar而不是self.bar 初始

5

仅仅因为您在抽象基类中将其定义为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> 

如果你想在__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> 
+0

很遗憾ABC检查不是在实例初始化时发生的,而是在类定义的初始化过程中发生的,这样抽象属性就成为可能。这将允许我们有一个abstractattribute_or_property - 这正是我想要的 – chris

+0

在__init__中添加一个attr对我不起作用。 python3.6 –

+0

@ArtemZhukov上面的例子是IPython中的Python 3.6会话,它可以工作。不过,您也需要类体中的'required = None',如上所示。 – Anentropic

0

https://docs.python.org/2/library/abc.html你可以做这样的事情在Python 2.7:

from abc import ABCMeta, abstractproperty 


class Test(object): 
    __metaclass__ = ABCMeta 

    @abstractproperty 
    def test(self): yield None 

    def get_test(self): 
     return self.test 


class TestChild(Test): 

    test = None 

    def __init__(self, var): 
     self.test = var 


a = TestChild('test') 
print(a.get_test())