I'm working on a project using abstract classes in Python (specifically, the abc module).
I have a few implementations of this abstract class, which have their own constructors and need to use self
.
This is what my code looks like, but simplified:
from abc import ABC, abstractmethod
class BaseClass(ABC):
def __init__(self):
self.sublinks = [] # not meant to be passed in, that's why it isn't an argument in __init__
@classmethod
def display(cls):
print(cls.get_contents())
@abstractmethod
def get_contents():
pass
class ImplementationOne(Base):
def __init__(self, url):
self.url = url
def get_contents(self):
return "The url was: " + url
class ImplementationTwo(Base):
def get_contents():
return "This does not need a url"
test_one = ImplementationOne("https://google.com")
test_two = ImplementationTwo()
test_one.display()
When I run this, however, I get the error TypeError: get_contents() missing 1 required positional argument: 'self'
.
I figured that this is because get_contents()
in ImplementationOne takes self
, but it's not specified in the abstract method.
So, if I changed:
@abstractmethod
def get_contents():
pass
to
@abstractmethod
def get_contents(self):
pass
But I get the same error.
I've tried many combinations, including putting self
as an argument to every occurrence or get_contents
, and passing in cls
to get_contents
in the abstract class - but no luck.
So, pretty much, how can I use the self
keyword (aka access attributes) in only some implementations of an abstract method, that's called within a class method in the abstract class itself.
Also, on a side note, how can I access self.sublinks
from within all implementations of BaseClass, while having its values different in each instance of an implementation?
There are a few things wrong here. One is that the @classmethod
decorator should only be used when you need it to be called on a class.
Example:
class ImplementationOne:
@classmethod
def display(cls):
print(f'The class name is {cls.__name__}.')
ImplementationOne.display()
There is nothing special about the name self
. It's just what is used by everyone to refer to the instance. In python the instance is implicitly handed to the first argument of the class unless you have a @classmethod
decorator. In that case the class is handed as the first argument.
That is why you are getting the TypeError
. Since you are calling the method on the instance test_one.display()
you are essentially calling it as an instance method. Since you need to access the instance method get_contents
from within it that is what you want. As a classmethod
you wouldn't have access to get_contents
.
That means you need both the ABC and ImplementationOne
to have those methods implemented as instance methods.
Since it is now an instance method on the ABC it also should be an instance method in ImplementationTwo
.
Your other question was how to get self.sublinks
as an attribute in both subclasses.
Since your are overriding __init__
in ImplementationOne
you need to call the parent class's __init__
as well. You can do this by using super()
to call the Super or Base class's methods.
class ImplementationOne(BaseClass):
def __init__(self, url):
self.url = url
super().__init__()
Full working code:
from abc import ABC, abstractmethod
class BaseClass(ABC):
def __init__(self):
self.sublinks = []
def display(self):
print(self.get_contents())
@abstractmethod
def get_contents(self):
pass
class ImplementationOne(BaseClass):
def __init__(self, url):
self.url = url
super().__init__()
def get_contents(self):
return "The url was: " + self.url
class ImplementationTwo(BaseClass):
def get_contents(self):
return "This does not need a url"
test_one = ImplementationOne("https://google.com")
test_two = ImplementationTwo()
test_one.display()
test_two.display()
print(test_one.sublinks)