How I ended up doing it.
class C(metaclass=ABCMeta):
@property
def x(self):
...
@x.setter
@abstractmethod
def x(self, val):
...
class D(C):
@C.x.setter
def x(self, val):
...
Answer from jack sexton on Stack OverflowHow I ended up doing it.
class C(metaclass=ABCMeta):
@property
def x(self):
...
@x.setter
@abstractmethod
def x(self, val):
...
class D(C):
@C.x.setter
def x(self, val):
...
You'll need a little bit of indirection. Define the setter as you normally would, but have it call an abstract method that does the actual work. Then each child class will need to provide a definition of that method. For example,
class Base(object):
__metaclass__ = abc.ABCMeta
def __init__(self):
self._val = 3
@property
def val(self):
return self._val
@val.setter
def val(self, x):
self._val_setter(x)
@abc.abstractmethod
def _val_setter(self, x):
pass
class Child(Base):
def _val_setter(self, x):
self._val = 2*x
Then
>>> c = Child()
>>> print c.val
3
>>> c.val = 9
>>> print c.val
18
I come from a C++ background, and I need to write an abstract base class, that inherits from abc. In this abstract base class, I would like to "declare" (this is C++ lingo, but I don't think variable declaration is a thing in Python) an uninstantiated variable, say `var`, which is initialized in the subclasses.
I'm wondering if there's any way to do this in Python?
After some thought, I think the second code snippet does not use the Factory method as it is not concerned with the creation of objects.
However, the code can be refactored to use the Factory method and better communicate the intent:
class CraneInterface(ABC):
def __init__(self):
self.__axis = self._make_axis()
@property
def axis(self) -> AxisInterface:
return self.__axis
@abstractmethod
def _make_axis(self) -> AxisInterface:
"""
Factory method: "Subclasses override to change the class of object that will be created"
"""
pass
To re-iterate the most important part of your quote from Refactoring Guru:
Factory Method defines a method, which should be used for creating objects instead of direct constructor call.
If the function that returns an AxisInterface does not have the purpose of creating an axis for the caller, then you are not using the Factory Method pattern, regardless of how much your code resembles the implementation of the pattern.
Design patterns are not defined by their structure but by the intent of the code.
Since Python 3.3 a bug was fixed meaning the property() decorator is now correctly identified as abstract when applied to an abstract method.
Note: Order matters, you have to use @property above @abstractmethod
Python 3.3+: (python docs):
from abc import ABC, abstractmethod
class C(ABC):
@property
@abstractmethod
def my_abstract_property(self):
...
Python 2: (python docs)
from abc import ABCMeta, abstractproperty
class C:
__metaclass__ = ABCMeta
@abstractproperty
def my_abstract_property(self):
...
Until Python 3.3, you cannot nest @abstractmethod and @property.
Use @abstractproperty to create abstract properties (docs).
from abc import ABCMeta, abstractmethod, abstractproperty
class Base(object):
# ...
@abstractproperty
def name(self):
pass
The code now raises the correct exception:
Traceback (most recent call last):
File "foo.py", line 36, in
b1 = Base_1('abc')
TypeError: Can't instantiate abstract class Base_1 with abstract methods name
It seems there's a discrepancy here; using @property along with @abstractmethod doesn't seem to enforce classes that inherit from your abc to need to define both setter and getter. Using this:
@property
@abstractmethod
def myProperty(self):
pass
@myProperty.setter
@abstractmethod
def myProperty(self):
pass
and then providing an implementation only for the getter in the class works and allows for instantiation:
@property
def myProperty(self):
return self._myProperty
This is due to the fact that only one name (myProperty) appears in the namespace of the ABC, when you override in the base class, you only need to define this one name.
There's a way around that enforces it. You can create separate abstract methods and pass them on to property directly:
class MyAbstractClass(ABC):
@abstractmethod
def getProperty(self):
pass
@abstractmethod
def setProperty(self, val):
pass
myAbstractProperty = property(getProperty, setProperty)
Providing an implementation for this abc now requires both getter and setter to have an implementation (both names that have been listed as abstractmethods in MyAbstractClass namespace need to have an implementation):
class MyInstantiatableClass(MyAbstractClass):
def getProperty(self):
return self._Property
def setProperty(self, val):
self._Property = val
myAbstractProperty = property(getProperty, setProperty)
Implementing them is exactly the same as any old property. There's no difference there.
For example, you can define the abstract getter, setter and deleter in Person abstract class, override them in Student class which extends Person abstract class as shown below. *@abstractmethod must be the innermost decorator otherwise error occurs:
from abc import ABC, abstractmethod
class Person(ABC):
@property
@abstractmethod # The innermost decorator
def name(self): # Abstract getter
pass
@name.setter
@abstractmethod # The innermost decorator
def name(self, name): # Abstract setter
pass
@name.deleter
@abstractmethod # The innermost decorator
def name(self): # Abstract deleter
pass
class Student(Person):
def __init__(self, name):
self._name = name
@property
def name(self): # Overrides abstract getter
return self._name
@name.setter
def name(self, name): # Overrides abstract setter
self._name = name
@name.deleter
def name(self): # Overrides abstract deleter
del self._name
Then, you can instantiate Student class and call the getter, setter and deleter as shown below:
obj = Student("John") # Instantiates "Student" class
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))
Output:
John
Tom
False
You can see my answer which explains more about abstract property.
The problem is that neither the getter nor the setter is a method of your abstract class; they are attributes of the property, which is a (non-callable) class attribute. Consider this equivalent definition:
def status_getter(self):
pass
def status_setter(self, value):
pass
class Component(metaclass=abc.ABCMeta):
# status = property(...)
# status.__isabstractmethod__ = True
status = abstractmethod(property(status_getter, status_setter))
Inheriting a property is quite different from inheriting a method. You are basically replacing the property, because your class itself does not have a reference to either the getter or the setter. Despite the name, abstractmethod does not actually make the property a method; it really does nothing more than add an attribute to whatever it is applied to and return the original value.
So, to ensure that a subclass provides a read/write property, what are you to do? Skip the decorator syntax, define the getter and setter as explicit abstract methods, then define the property explicitly in terms of those private methods.
class Component(metaclass=abc.ABCMeta):
@abstractmethod
def _get_status(self):
pass
@abstractmethod
def _set_status(self, v):
pass
status = property(lambda self: self._get_status(), lambda self, v: self._set_status(self, v))
Or, you can make use of __init_subclass__ (which postdates abc; its purpose is to allow class initialization that is otherwise only possible via a metaclass).
class Component:
def __init_subclass(cls, **kwargs):
super().__init_subclass__(**kwargs)
try:
p = cls.status
except AttributeError:
raise ValueError("Class does not define 'status' attribute")
if not isinstance(p, property):
raise ValueError("'status' is not a property")
if p.fget is None:
raise ValueError("'status' has no getter")
if p.fset is None:
raise ValueError("'status' has no setter")
This is actually an improvement over abc, in my opinion. If a subclass fails to define a read/write status property, an exception will be raised when the class is defined, not just when you attempt to instantiate the class.
from abc import ABCMeta, abstractmethod
class Base(object):
__metaclass__ = ABCMeta
def __init__(self, val):
self._foo = val
@abstractmethod
def _doStuff(self, signals):
print ('Base does stuff')
@abstractmethod
def _get_foo(self):
return self._foo
@abstractmethod
def _set_foo(self, val):
self._foo = val + 'r'
foo = property(_get_foo, _set_foo)
class floor_1(Base):
__metaclass__ = ABCMeta
def __init__(self, val):
self._foo = val
super(floor_1, self).__init__(val)
def _doStuff(self, signals):
print ('floor_1 does stuff')
def _get_foo(self):
return self._foo
def _set_foo(self, val):
#self._foo = val + 'r'
super()._set_foo(val + 'r')
foo = property(_get_foo, _set_foo)
class floor_2(floor_1):
@property
def foo(self):
return self._foo
@foo.setter
def foo(self, val):
self._foo = val + 'r'
#super()._set_foo(val + 'r')
b1 = floor_1('bar')
# b1 = floor_2('bar')
print(b1.foo)
b1.foo = 'bar'
print(b1.foo)