Как правильно дублировать подпись базового класса '__init__' в Python? - PullRequest
1 голос
/ 31 марта 2020

Если подкласс не определяет свой собственный метод __init__, конструктор базового класса (следовательно, его сигнатура) автоматически наследуется. Но как определить метод подкласса __init__, который наследует сигнатуру базового класса (автоматически)?

Например:

class Base:
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

class Child1(Base):
    def foo(self):
        return self.arg1 + self.arg2


class Child2(Base):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        if self.arg1 == 0:
            raise ValueError("arg1 cannot be zero")

    def foo(self):
        return self.arg2 / self.arg1

Child1 не имеет его собственный конструктор, поэтому Base __init__ наследуется, и help(Child1) даст:

Help on class Child1 in module test:

class Child1(Base)
 |  Child1(arg1, arg2)
 |  
 ...

Child2 должен иметь пользовательский __init__. Однако, поскольку он просто передает все аргументы Base, его аргументы определяются как (*args, **kwargs), чтобы замкнуть все в Base. Но это дает следующую подпись:

Help on class Child2 in module test:

class Child2(Base)
 |  Child2(*args, **kwargs)
 |  
 ...

, что гораздо менее информативно. Есть ли способ сказать "Я хочу, чтобы у моего __init__ была такая же подпись, как у Base.__init__"?

Ответы [ 2 ]

1 голос
/ 01 апреля 2020

Этот ответ является описанием того, как я нашел разумное решение, поэтому, пожалуйста, потерпите меня или просто прокрутите до декоратора TL; DR'd в конце.

Давайте начнем с того, что help есть и делает. help добавляется во встроенное пространство имен с помощью site. Реализация по умолчанию на моей машине с Ubuntu перенаправляет все на pydoc.help. Это в свою очередь использует inspect для получения подписей и описаний. Вас интересуют только функции, а точнее __init__. Кроме того, вы заботитесь только о подписи, а не об остальных документах. Это должно упростить ситуацию.

Мы можем с уверенностью предположить, что подпись, которую вы видите в help / pydoc, генерируется inspect.signature. Посмотрите на исходный код этой функции для Python 3.8.2 и проследите через inspect.Signature.from_callable -> inspect._signature_from_callable -> В строке 2246 мы видим возможное решение.

Суть в том, что если у объекта функции есть атрибут __signature__, и этот атрибут является экземпляром inspect.Signature, она будет использоваться в качестве сигнатуры функции, без пересчета ее из обычного осмотра объектов __code__ и __annotation__.

Еще один момент в нашу пользу это что функции являются первоклассными объектами с атрибутом __dict__, которым могут быть назначены произвольные ключи. Назначение __signature__ вашей функции не повлияет на ее выполнение, поскольку она используется только для проверки. Фактическая сигнатура времени выполнения определяется в объекте __code__ с помощью таких атрибутов, как co_argcount, co_kwonlyargcount, co_varnames, et c.

. Поэтому вы можете просто сделать:

import inspect

Child2.__init__.__signature__ = inspect.signature(Child1.__init__)

Результат:

>>> help(Child1)
Help on class Child1 in module __main__:

class Child1(Base)
 |  Child1(arg1, arg2)
 |  
 |  Method resolution order:
 |      Child1
 |      Base
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  foo(self)
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from Base:
 |  
 |  __init__(self, arg1, arg2)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from Base:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

>>> help(Child2)
Help on class Child2 in module __main__:

class Child2(Base)
 |  Child2(arg1, arg2)
 |  
 |  Method resolution order:
 |      Child2
 |      Base
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  __init__(self, arg1, arg2)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  foo(self)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from Base:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Оба конструктора продолжают работать как обычно, кроме этого.

Поскольку это не изменяет объект кода или даже аннотации, изменение вряд ли повлияет все, что касается работы функции.

TL; DR

Вот декоратор, который можно использовать для копирования сигнатур функций без вмешательства в функцию в любом Другой способ:

import inspect

def copy_signature(base):
    def decorator(func):
        func.__signature__ = inspect.signature(base)
        return func
    return decorator

И вы можете переписать Child2 как

class Child2:

    @copy_signature(Child1.__init__)
    def __init__(self, *args, **kwargs):
        ...
0 голосов
/ 31 марта 2020

Конечно, просто укажите это явно:

class Child2(Base):
    def __init__(self, arg1, arg2):
        if arg1 == 0:
            raise ValueError("arg1 cannot be zero")
        super().__init__(arg1, arg2)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...