Как мне переопределить атрибут родительского класса в дочернем классе без использования экземпляров объектов любого класса? Исходя из мира Java / C ++ и их строгих структурных решений, Мне бросается в глаза способ работы Python. Я хотел бы оставаться относительно статичным.

Пример:

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

Желаемое использование :

>>> config.print_url()
/host/config

>>> log.print_url()
/host/log

Я хочу, чтобы атрибуты config.path и log.path переопределяли base.path. Так я могу использовать url = urljoin(host, path) раз и навсегда в классе base (и избежать необходимости копировать / вставлять тот же атрибут / вычисление в каждый производный класс).

Я не могу понять, как этого добиться без создания объектов (чего я надеюсь избежать). У кого-нибудь есть совет? Заранее спасибо!

1
yona 26 Сен 2020 в 04:55

1 ответ

Лучший ответ

Дочерние атрибуты path переопределяют base.path. Вы не переопределяете атрибут url. Это вычисляется один раз, когда тело base запускается для создания объекта класса.

У вас есть несколько вариантов на будущее. В любом случае вам нужно заставить url вычислять динамически, либо каждый раз при обращении к нему, либо хотя бы один раз для каждого дочернего класса.

Самый простой способ - превратить url в 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"

Обратите внимание, что вы сейчас имеете в виду актуальные классы host и path на лету. Вам также понадобится только один метод print_url в base вместо разных в каждом классе.

Другой вариант - предоставить base и, следовательно, всем его дочерним элементам метакласс с url в качестве 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"

Это работает, потому что классы python тоже являются объектами. Вы можете определить property в классе класса (метаклассе), и он будет вести себя так же, как любой property по отношению к экземпляру. На этот раз экземпляр - это сам класс.

Третий вариант - убедиться, что url определен статически, но правильно для каждого дочернего элемента. Это можно сделать с помощью метода __init_subclass__ очень удобно прямо из 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"

То же самое можно сделать и с метаклассом:

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"
2
Mad Physicist 26 Сен 2020 в 02:36