Декоратор класса Python, который переопределяет внутренний метод

#python #oop #inheritance #overriding #decorator

Вопрос:

Я пытаюсь написать декоратор, который можно использовать для переопределения метода во время наследования, но внешне. Что-то вроде:

 class A:
   def some_method(self):
       print('does stuff')

def override(method):
    # override decorator
    ....

def some_method_override():
    print('does other stuff')

@override(method, some_method_override)
class B:
    #overridden

a = A()
b = B()
a.some_method() # prints 'does stuff'
b.some_method() # prints 'does other stuff'
 

Если бы кто-нибудь мог поделиться некоторыми идеями по этому поводу, это было бы здорово!

Комментарии:

1. Почему? Это кажется довольно странной вещью, без реальной пользы и нескольких странных предостережений (например, нарушение одного аргумента super ).

2. @user2357112supportsMonica потенциально может иметь множество классов, наследуемых от A, и переопределять определенный метод таким же образом, но изменять другие, чтобы вы могли повторно использовать методы

3. Я бы заглянул в макет библиотеки, если вы пытаетесь динамически переопределять методы. Вот ресурс — docs.python.org/3/library/unittest.mock.html

4. (упс, означало нарушение супер с 0 аргументами в моем предыдущем комментарии-супер с одним аргументом — это странный исторический артефакт, который почти никогда ни для чего не пригодится)

Ответ №1:

Вот как я бы решил эту проблему на основе прецедента;

  1. ДЛЯ ТЕСТОВ — я бы заглянул в библиотеку макетов, если вы пытаетесь динамически переопределять методы. Вот ресурс — https://docs.python.org/3/library/unittest.mock.html
  2. ДЛЯ ОТДЕЛЬНОГО КЛАССА — если это изменение для всего класса, я бы выбрал дочерний класс, который переопределяет исходный метод; это сделает ваш код намного более явным
     class A:
        def some_method(self):
            print('do_stuff_a')
    
    class B(A):
        def some_method(self):
            print('do_stuff_b')
    
    a = A()
    a.some_method() # print 'do_stuff_a'
    b = B()
    a.some_method() # prints 'do_stuff_b'
     
  3. ДЛЯ КОНКРЕТНОЙ СИТУАЦИИ — если вы хотите, чтобы такое поведение происходило только иногда, я бы использовал шаблон кода наблюдателя состояния- вот пример
     class A:
        def __init__(self)
            self._use_method_a = True
    
        def use_method_a(self, use_method_a):
            self._use_method_a = use_method_a
    
        def some_method(self):
            if self._use_method_a:
                return self._method_a()
            else:
                return self._method_b()
    
        def _method_a(self):
            print('do_stuff_a')
    
        def _method_b(self):
            print('do_other_stuff_b')
    
    a = A()
    a.some_method() # print 'do_stuff_a'
    a.use_method_a(False) 
    a.some_method() # prints 'do_other_stuff_b'
     

Ответ №2:

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

Наследование не происходит, когда определен класс; методы из родительского класса не «втягиваются» в дочерний класс. Скорее всего, это происходит во время поиска атрибутов. Если B.some_method не определено, то A.some_method используется значение. Поэтому, чтобы переопределить метод родительского класса, вы просто предоставляете определение нужного атрибута класса. Методы-это просто атрибуты класса со значениями типа function .

 class A:
   def some_method(self):
       print('does stuff')


def some_method_override(self):
    print('does other stuff')


class B(A):
    some_method = some_method_override

a = A()
b = B()
a.some_method() # prints 'does stuff'
b.some_method() # prints 'does other stuff'
 

Если бы вам действительно нужен был декоратор, это было бы просто что-то вроде

 def override(old: str, new: Callable):
    def _(cls):
        setattr(cls, old, new)
        return cls
    return _


def some_method_override(self):
    print('does other stuff')


class A:
   def some_method(self):
       print('does stuff')


@override('some_method', some_method_override)
class B(A):
    pass