定义简单抽象属性的最优雅方法

时间:2019-07-29 18:00:20

标签: python python-3.x abstract-class abstract

我正在尝试使用属性来实现抽象类,但我无法简单地定义它。

我只想定义属性名称以约束子类拥有它,但是我不想在继承我的抽象类的每个类中复制/粘贴getter和setter。 这是我找到的解决方案,但我认为不是很好:

  • 也许是最有效,最强大的方法,但是却很丑陋和多余。我们必须在每个子类中放入“ def a():pass”
class AbstractC(ABC):

   @property
   @abstractmethod
   def a(self):
       pass

class ConcreteC1(AbstractC):
   def __init__(self, name):
       self.a = name

   def a(self):
       pass

class ConcreteC2(AbstractC):
   def __init__(self, name):
       self.a = name

class ConcreteC3(AbstractC):

   def __init__(self, name):
       self.poney = name

ConcreteC1('foobar') # ok
ConcreteC2('foobar') # error !
ConcreteC3('foobar') # error !
  • 同样,但更丑的imo
class AbstractC(ABC):

   @property
   @abstractmethod
   def a(self):
       pass

class ConcreteC1(AbstractC):
   a = None

   def __init__(self, name):
       self.a = name

class ConcreteC2(AbstractC):
   def __init__(self, name):
       self.a = name

class ConcreteC3(AbstractC):
   def __init__(self, name):
       self.poney = name

ConcreteC1('foobar') # ok
ConcreteC2('foobar') # error !
ConcreteC3('foobar') # error !
  • 最紧凑的方式,但不可靠。如果缺少“ a”,则没有错误
class AbstractC(ABC):

   @abstractmethod
   def __init__(self, val):
       self.a = val


class ConcreteC1(AbstractC):
   def __init__(self, name):
       self.a = name

class ConcreteC2(AbstractC): 
   def __init__(self, name):
       self.poney = name

ConcreteC1('foobar') # ok
ConcreteC2('foobar') # no error !

那么有没有一种方法可以获取具有抽象属性的优雅,健壮和紧凑的抽象类?还是我试图得到一些不可能的东西?我正在考虑接近的东西:

class AbstractC(ABC):

   @property
   @abstractmethod
   def a(self):
       pass

class ConcreteC(AbstractC):
   def __init__(self, name):
       self.a = name

如果没有这样的解决方案,最好的解决方案是什么?

3 个答案:

答案 0 :(得分:2)

您可能会误用mongodump进行花式继承

namedtuples

但是,恕我直言,如果您提出from collections import namedtuple BaseAttributes = namedtuple('base', ['attr1', 'attr2']) print(BaseAttributes('one', 2)) class SomethingElse(BaseAttributes): def method(self): return 3 blubb = SomethingElse('A', 5) blubb.method() ,例如,您的最后一个建议是有意义的:

NotImplementedError

答案 1 :(得分:1)

也许这会有所帮助。我做了一个继承自ABC的类。它定义了在创建新的子类之后调用的方法__init_subclass__。它执行下一个操作:
对于声明的每个抽象属性,在子类中搜索相同的方法。如果存在(它是一个函数对象),则将其转换为属性,并将其替换为子类字典。

from abc import ABC, abstractmethod

class Foo(ABC):
    def __init_subclass__(cls):
        super().__init_subclass__()

        ###### This is the new part. I explain it at the end of the answer
        for name, value in attrs.items():
            if name not in cls.__dict__:
                setattr(cls, name, property(lambda *args, **kwargs: value))
        ######

        # Iterate throught all abstract methods on the class
        for name in Foo.__abstractmethods__:
            absmethod = Foo.__dict__[name]
            # Check if the abstract method is a property
            if not isinstance(absmethod, property):
                continue

            # Check if there is a method defined in the subclass with the same name
            if name not in cls.__dict__ or not callable(cls.__dict__[name]):
                continue
            method = cls.__dict__[name]

            # If the method is not already a property, we decorate it automatically...
            if not isinstance(method, property):
                setattr(cls, name, property(method))

    @property
    @abstractmethod
    def a(self):
        return 1

现在定义一个子类并对其进行测试:


class Bar(Foo):
    def __init__(self):
        pass

    def a(self):
        return 2

    @property
    def b(self):
        return 3

obj = Bar()
print(obj.a)
print(obj.b)

输出将是:

2
3

下一个代码将引发错误,因为并非所有抽象方法都已实现:

class Qux(Foo):
    pass

编辑: 现在您也可以这样做:

class Bar(Foo, a=1):
    pass

print(Bar().a) # 1

答案 2 :(得分:1)

仍然存在问题。如果我选择引发错误的实现,则必须向该方法中添加@media (max-width: 600px) { .toolbarbutton a { text-decoration: none; } } 或我可以调用ConcreteC()。a,即使未设置a也不会引发错误:

@property

但是,如果我添加class AbstractC(ABC): def a(self): raise NotImplementedError('Implement _a_ method') class ConcreteC(AbstractC): def __init__(self, val): super().__init__() self.poney = val In [3]: ConcreteC('foobar').a Out[3]: <bound method AbstractC.a of <__main__.ConcreteC object at 0x7f2e1c6b0518>> 则会收到错误消息:

@property

编辑:

这是我选择的解决方案:

class AbstractC(ABC):

    @property
    def a(self):
        raise NotImplementedError('Implement _a_ method')

class ConcreteC(AbstractC):
    def __init__(self, val):
        super().__init__()
        self.a = val

In [4]: ConcreteC('foobar')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-55-587237cb76e5> in <module>
----> 1 ConcreteC('foobar')

~/workspace/draft.py in __init__(self, val)
    151     def __init__(self, val):
    152         super().__init__()
--> 153         self.a = val
    154 
    155 

AttributeError: can't set attribute

这样,我可以非常简单地编辑“ a”,如果没有定义,则会在get上引发异常。我不知道要使设置器工作,它必须与属性名称相同。 最后,我想要的不是抽象属性,而是抽象类中的具体属性。

class AbstractC(ABC):

    @property
    def a(self):
        try:
            return self._a
        except AttributeError:
            raise NotImplementedError('Implement _a_ method')

    @a.setter
    def a(self, val):
        self._a = val


class ConcreteC(AbstractC):
    def __init__(self, val):
        self.a = val