Attributes of abstract class in Python - python

I have been learning OOPS in python and finding it a bit different from that of Java. In the below code I have written an abstract class and implemented it. Is it the right way to define the attributes of an abstract class?
class Vehicle(ABC):
#property
#abstractmethod
def color(self):
pass
#property
#abstractmethod
def regNum(self):
pass
class Car(Vehicle):
def __init__(self,color,regNum):
self.color = color
self.regNum = regNum
Is this a better way to do this?
from abc import ABC, abstractmethod
class Vehicle(ABC):
#abstractmethod
def __init__(self,color,regNum):
self.color = color
self.regNum = regNum
class Car(Vehicle):
def __init__(self,color,regNum):
self.color = color
self.regNum = regNum
car = Car("red","EX9890")

If you want to define abstract properties in an abstract base class, you can't have attributes with the same names as those properties, and you need to define concrete implementations of the properties in the concrete child class:
from abc import ABC, abstractmethod
class Vehicle(ABC):
#property
#abstractmethod
def color(self):
pass
#property
#abstractmethod
def regNum(self):
pass
class Car(Vehicle):
def __init__(self, color, regNum):
self._color = color
self._regNum = regNum
#property
def color(self):
return self._color
#property
def regNum(self):
return self._regNum
c = Car("foo", "bar") # works fine; would not work if abstract methods weren't implemented

Related

What is the conventional way in Python for defining attributes in an abstract base class? [duplicate]

In the following code, I create a base abstract class Base. I want all the classes that inherit from Base to provide the name property, so I made this property an #abstractmethod.
Then I created a subclass of Base, called Base_1, which is meant to supply some functionality, but still remain abstract. There is no name property in Base_1, but nevertheless python instatinates an object of that class without an error. How does one create abstract properties?
from abc import ABCMeta, abstractmethod
class Base(object):
# class Base(metaclass = ABCMeta): <- Python 3
__metaclass__ = ABCMeta
def __init__(self, str_dir_config):
self.str_dir_config = str_dir_config
#abstractmethod
def _do_stuff(self, signals):
pass
#property
#abstractmethod
def name(self):
"""This property will be supplied by the inheriting classes
individually.
"""
pass
class Base1(Base):
__metaclass__ = ABCMeta
"""This class does not provide the name property and should
raise an error.
"""
def __init__(self, str_dir_config):
super(Base1, self).__init__(str_dir_config)
# super().__init__(str_dir_config) <- Python 3
def _do_stuff(self, signals):
print "Base_1 does stuff"
# print("Base_1 does stuff") <- Python 3
class C(Base1):
#property
def name(self):
return "class C"
if __name__ == "__main__":
b1 = Base1("abc")
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 ABC, abstractproperty
class C(ABC):
#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
Based on James answer above
def compatibleabstractproperty(func):
if sys.version_info > (3, 3):
return property(abstractmethod(func))
else:
return abstractproperty(func)
and use it as a decorator
#compatibleabstractproperty
def env(self):
raise NotImplementedError()
In python 3.6+, you can also anotate a variable without providing a default. I find this to be a more concise way to make it abstract.
class Base():
name: str
def print_name(self):
print(self.name) # will raise an Attribute error at runtime if `name` isn't defined in subclass
class Base_1(Base):
name = "base one"
it may also be used to force you to initialize the variable in the __new__ or __init__ methods
As another example, the following code will fail when you try to initialize the Base_1 class
class Base():
name: str
def __init__(self):
self.print_name()
class Base_1(Base):
_nemo = "base one"
b = Base_1()
AttributeError: 'Base_1' object has no attribute 'name'
Using the #property decorator in the abstract class (as recommended in the answer by James) works if you want the required instance level attributes to use the property decorator as well.
If you don't want to use the property decorator, you can use super(). I ended up using something like the __post_init__() from dataclasses and it gets the desired functionality for instance level attributes:
import abc
from typing import List
class Abstract(abc.ABC):
"""An ABC with required attributes.
Attributes:
attr0
attr1
"""
#abc.abstractmethod
def __init__(self):
"""Forces you to implement __init__ in 'Concrete'.
Make sure to call __post_init__() from inside 'Concrete'."""
def __post_init__(self):
self._has_required_attributes()
# You can also type check here if you want.
def _has_required_attributes(self):
req_attrs: List[str] = ['attr0', 'attr1']
for attr in req_attrs:
if not hasattr(self, attr):
raise AttributeError(f"Missing attribute: '{attr}'")
class Concrete(Abstract):
def __init__(self, attr0, attr1):
self.attr0 = attr0
self.attr1 = attr1
self.attr2 = "some value" # not required
super().__post_init__() # Enforces the attribute requirement.
For example, you can define the abstract getter, setter and deleter with #abstractmethod and #property, #name.setter or #name.deleter in 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
Then, you can extend Person abstract class with Student class, override the abstract getter, setter and deleter in Student class, instantiate Student class and call the getter, setter and deleter as shown below:
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
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
Actually, even if you don't override the abstract setter and deleter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
#property
def name(self): # Overrides only 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
obj = Student("John") # Instantiates "Student" class
# ...
No error occurs as shown below:
John
Tom
False
But, if you don't override the abstract getter, setter and deleter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
# #property
# def name(self): # Overrides only 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
obj = Student("John") # Instantiates "Student" class
# ...
The error below occurs:
TypeError: Can't instantiate abstract class Student with abstract methods name
And, if you don't override the abstract getter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
# #property
# def name(self): # Overrides only 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
obj = Student("John") # Instantiates "Student" class
# ...
The error below occurs:
NameError: name 'name' is not defined
And, if #abstractmethod is not the innermost decorator as shown below:
from abc import ABC, abstractmethod
class Person(ABC):
#abstractmethod # Not the innermost decorator
#property
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
The error below occurs:
AttributeError: attribute 'isabstractmethod' of 'property' objects is not writable
Another possible solution is to use metaclasses.
A minimal example can look like this:
class BaseMetaClass(type):
def __new__(mcls, class_name, bases, attrs):
required_attrs = ('foo', 'bar')
for attr in required_attrs:
if not attr in attrs:
raise RunTimeError(f"You need to set {attr} in {class_name}")
return super().__new__(mcls, class_name, bases, attrs)
class Base(metaclass=BaseMeta):
foo: str
bar: int
One advantage of this approach is that the check will happen at definition time (not instantiation).
Also, setting class attributes in child classes is a bit easier than declaring properties (as long as they are simple values known in advance) and your final classes will look more concise

Python: ensuring all subclass methods return the same thing

In Python 3, I have class Animal as an abstract base class, with abstract method give_name(self). Derived classes include Dog and Cat.
The start of the contents of give_name() method can can differ across subclasses. For example, in the Dog subclass, there might be print("bark") while in the Cat subclass, there may be print("meow").
However, I want to make sure that in all derived classes, give_name() ends with return self.__class__.__name__. How can I enforce this constraint (as elegantly as possible)?
Current Code:
from abc import ABC, abstractmethod
class Animal(ABC):
#abstractmethod
def give_name(self):
pass
class Dog(Animal):
def give_name(self):
print("bark")
return self.__class__.__name__
class Cat(Animal):
def give_name(self):
print("meow")
return self.__class__.__name__
You could implement give_name() in the superclass, but delegate the print part to subclasses, e.g.
from abc import ABC, abstractmethod
class Animal(ABC):
def give_name(self):
self.print()
return self.__class__.__name__
#abstractmethod
def print(self):
pass
class Dog(Animal):
def print(self):
print("bark")
class Cat(Animal):
def print(self):
print("meow")

How to subclass an Abstract Base Class?

Let's say I have an ABC:
class Template_ABC(metaclass=abc.ABCMeta):
def __init__(self, data=None, model=None):
self._data = data
self._model = model
#abc.abstractmethod
def do_stuff(self):
pass
#abc.abstractmethod
def do_more_stuff(self):
pass
I normally have a class of ABC, for example:
class Example_of_ABC(Template_ABC):
def do_stuff(self):
# Do stuff here
def do_more_stuff(self):
pass
Now, I want to subclass Example-of_ABC class. The only way I can do is as as follows:
class Subclass_of_Example_of_ABC(Example_of_ABC):
def __init__(self, data=None, model=None):
super().__init__(data, model)
def do_more_stuff(self):
# Do more stuff here
The issue with this way, is that I have to update my def _init__() for every subclass of the ABC. Is there anyway for the subclass to inherit all the inits from the ABC?

How to define a method in class that inherits from Abstract Base Class

I have two abstract Classes with two methods: getArea(width, height) and getPerimeter(width, height). Those methods are abstract too, so they can be (or not) implemented in a derived class (In my case they must be implemented). In C# I could write IRectangle.getArea(){} or IParallelogram.getPerimeter(){} and simply make implementation. How can I do this in Python? I think that I have to use something like super(IRectangle, self).getArea() but I am not sure how.
from abc import ABC, abstractmethod
class IRectangle(ABC):
#abstractmethod
def getArea(width, height):
pass
#abstractmethod
def getPerimeter(width, height):
pass
class IParallelogram(ABC):
#abstractmethod
def getArea(parHeight, parBase):
pass
#abstractmethod
def getPerimeter(parHeight, parBase):
pass
class Calculate (IParallelogram, IRectangle):
You would just make a class that inherits from the abstract class and overrides the abstract methods like you would write a regular method.
class A(ABC):
#abstractmethod
def f(self):
pass
class B(A):
def f(self):
print ('hello')
If you want to override it but make it do nothing:
class B(A):
def f(self):
pass
Your class hierarchy is upside down. First, you would define a more generic class like Shape as the (abstract) root of the hierarchy.
class Shape(ABC):
#abstractmethod
def getArea(self):
pass
#abstractmethod
def getPerimeter(self):
pass
Then, the concrete classes IRectangle and IParallelogram would implement getArea and getPerimeter appropriately.
class IRectangle(Shape):
def getArea(self):
...
def getPerimeter(self):
...
class IParallelogram:
def getArea(self):
...
def getPerimeter(self):
...

How to make an Abstract Class inherit from another Abstract Class in Python?

Is it possible to have an Abstract Class inheriting from another Abstract Class in Python?
If so, how should I do this?
Have a look at abc module. For 2.7: link. For 3.6: link
Simple example for you:
from abc import ABC, abstractmethod
class A(ABC):
def __init__(self, value):
self.value = value
super().__init__()
#abstractmethod
def do_something(self):
pass
class B(A):
#abstractmethod
def do_something_else(self):
pass
class C(B):
def do_something(self):
pass
def do_something_else(self):
pass
from abc import ABCMeta, abstractmethod
class OriginalAbstractclass(metaclass=ABCMeta):
#abstractmethod
def sample_method(self):
pass
class InheritedAbstractClass(OriginalAbstractclass, metaclass=ABCMeta):
#abstractmethod
def another_method(self):
pass
class ConcreteClass(InheritedAbstractClass):
def some_method(self):
pass
def another_method(self):
pass

Categories

Resources