Reputation: 434
How do I go about overriding a parent class attribute in a child class without using object instances of either class? Coming from the world of Java/C++ and their strict structural designs, I'm finding myself challenged by Python's way of doing things. I'd like to stay relatively static.
Example:
from urllib.parse import urljoin
class base:
host = "/host/"
path = "Override this in child classes"
url = urljoin(host, path)
class config(base):
path = "config"
@classmethod
def print_url(cls):
print(cls.url) # Currently prints "/host/Override this in child classes"
# Would like to print "/host/config" instead
class log(base):
path = "log"
@classmethod
def print_url(cls):
print(cls.url) # Currently prints "/host/Override this in child classes"
# Would like to print "/host/log" instead
Desired Usage:
>>> config.print_url()
/host/config
>>> log.print_url()
/host/log
I'd like the config.path
and log.path
attributes to override base.path
. This is so I can use url = urljoin(host, path)
once and for all in the base
class (and avoid having to copy/paste that same attribute/calculation in every single derived class).
I can't figure out how to accomplish this without constructing objects (which I'm hoping to avoid). Anyone have any advice? Thanks in advance!
Upvotes: 4
Views: 3017
Reputation: 114518
The child path
attributes do override base.path
. What you don't override is the url
attribute. That gets computed once when the body of base
is run to create the class object.
You have a couple of options going forward. Either way you need to make url
compute dynamically, either every time it's accessed, or at least once per child class.
The simplest way is to make url
into a classmethod
:
class base:
host = "/host/"
path = "Override this in child classes"
@classmethod
def url(cls):
return urljoin(cls.host, cls.path)
@classmethod
def print_url(cls):
print(cls.url())
class config(base):
path = "config"
class log(base):
path = "log"
Notice that you're referring to the actual classe's host
and path
on the fly now. You also only need one print_url
method in base
instead of a different one in each class.
Another option is to give base
, and therefore all its children, a metaclass with url
as a property
:
class url_meta(type):
@property
def url(cls):
return urljoin(cls.host, cls.path)
class base(metaclass=url_meta):
host = "/host/"
path = "Override this in child classes"
@classmethod
def print_url(cls):
print(cls.url)
class config(base):
path = "config"
class log(base):
path = "log"
This works because python classes are objects too. You can define a property
in the class of a class (the metaclass), and it will behave as any property
does with respect to an instance. Just this time the instance is a class itself.
A third option is to make sure that url
is defined statically but correctly in each child. The __init_subclass__
method allows you to do that very conveniently directly from base
:
class base:
host = "/host/"
path = "Override this in child classes"
url = urljoin(host, path)
@classmethod
def __init_subclass__(cls):
cls.url = urljoin(cls.host, cls.path)
@classmethod
def print_url(cls):
print(cls.url)
class config(base):
path = "config"
class log(base):
path = "log"
You can accomplish the same thing with a metaclass as well:
class url_meta2(type):
def __init__(cls, *args, **kwargs):
cls.url = urljoin(cls.host, cls.path)
class base(metaclass=url_meta2):
host = "/host/"
path = "Override this in child classes"
@classmethod
def print_url(cls):
print(cls.url)
class config(base):
path = "config"
class log(base):
path = "log"
Upvotes: 4