Python: предпочтительный способ разрешить дочернему классу переопределять типы возвращаемых значений родительского метода? - PullRequest
1 голос
/ 05 августа 2020

Допустим, у меня есть два класса:

class A:
    def __init__(self):
        data = 0

    def copy(self):
        other = A()
        other.data = self.data
        return other 

class B(A):
    # B *does not* override A's __new__ or __init__
    @classmethod
    def from_A(cls, a):
        a.__class__ = cls
        return a

a = A()
b = B()

B.from_A(a)  # instance of B
b.copy()     # instance of A
B.from_A(b.copy() # ...    B

Проблема, конечно же, в том, что B().copy() возвращает экземпляр A. Я могу обойти это, определив метод-оболочку copy для B, или я могу вызвать B.from_A(B().copy()). Первый вариант требует обертывания всех таких методов; последний кажется опасным и вводит много шаблонов.

Есть ли более стандартный c способ Pythoni сделать это? Я спрашиваю, в частности, о случае, когда у меня нет доступа к реализации A, а соответствующие методы не являются методами классов. В качестве конкретного примера: я мог бы попытаться создать подкласс pandas.DataFrame и хочу убедиться, что B.copy() и B.merge(...) возвращают экземпляры B, а не DataFrame.

Ответы [ 2 ]

1 голос
/ 05 августа 2020

Общее решение этого обычно должно быть выполнено в A.copy() - оно должно быть спроектировано так, чтобы позволяло создавать подклассы и возвращало соответствующий подкласс

Не вызывать A() явно в метод copy(), вызовите конструктор класса для объекта.

def copy(self):
    other = type(self)()
    other.data = self.data
    return other 

Обратите внимание, что это не сработает, если подкласс добавляет дополнительные параметры в свой метод __init__.

0 голосов
/ 06 августа 2020

Может быть, что-то вроде этого уменьшит пластину котла:

class B(A):
    @classmethod
    def from_A(cls, a):
        a.__class__ = cls
        return a
    
    def wrap_method(self, method):
            out = getattr(self.__class__,method)(self)
            out.__class__ = self.__class__
            return out

Тогда вам нужно будет вызвать b.wrap_method('copy') вместо B.from_A(b.copy()), и они оба выведут экземпляр B:

B.from_A(b.copy()), b.wrap_method('copy')
>>> (<__main__.B at 0x7fb2915c20f0>, <__main__.B at 0x7fb2915c2400>)
...