декоратор для генерации новых классов в пространство имен - PullRequest
0 голосов
/ 28 июня 2018

Конкретный вариант использования, для которого он мне нужен, - не рекомендуется использовать имена классов.

Предположим, у нас есть класс A в более ранней версии, и мы хотим отказаться от его имени, но сохранить обратную совместимость:

class A(B):
    def __init__(self, *args, **kwargs):
        warnings.warn('deprecation!')
        super(A, self).__init__(*args, **kwargs)

... и B теперь имеет правильную реализацию.

Когда мы создаем класс A, мы столкнемся с предупреждением об устаревании. Мы также можем использовать модуль deprecated для декораторов на __init__.

Однако я хочу пропустить этот процесс и написать меньше кода, и, надеюсь, достичь чего-то вроде:

@deprecated_alias('A')
class B:
    # ... do something

Могу ли я как-то внедрить имя класса в пространство имен на уровне модуля, чтобы я мог использовать A, как это?

Ответы [ 2 ]

0 голосов
/ 28 июня 2018

Хотя это не совсем то, о чем вы просили, это существенно меньше волшебства и, в конечном счете, такое же количество строк кода. Это также намного более явно:

import warnings

def deprecated(DeprecatedByClass):
    class Deprecated(DeprecatedByClass):
        def __new__(cls, *args, **kwargs):
            warnings.warn("deprecation!")
            return super(Deprecated, cls).__new__(cls, *args, **kwargs)

    return Deprecated

Затем вы можете использовать это так:

class B:
    pass

A = deprecated(B)
0 голосов
/ 28 июня 2018

Могу ли я как-то внедрить имя класса в пространство имен на уровне модуля, чтобы я мог использовать A следующим образом?

Да. Класс декоратора должен:

  • создать новый тип с переопределенным методом __init__, используя вызов с 3 аргументами type
  • получить модуль исходного класса, sys.modules[original_class.__module__]
  • привязать новый класс в пространстве имен модуля, используя setattr
  • вернуть исходный класс без изменений

Пример: * * один тысяча двадцать-одна

import sys

def deprecated_alias(name):
    def decorator(class_):
        mod = sys.modules[class_.__module__]
        if hasattr(mod, name):
            raise Exception('uhoh, name collision')
        NewClass = type(name, (class_,), {'__init__': ...})
        setattr(mod, name, NewClass)
        return class_
    return decorator

@deprecated_alias('A')
class B:
    pass

Я не рекомендую такой подход - слишком много магии. Это запутает IDE и нарушит автозаполнение.

Возможно, менее магический подход? Это также может быть превращено в декоратор и использовать __subclasscheck__ / __subclasshook__, если вам нужно контролировать мельчайшие детали наследования.

class A(B):
    def __init__(self, *args, **kwargs):
        warnings.warn('deprecation!')
        return B(*args, **kwargs)
...