Использовать метод унаследованного класса в __init__

У меня есть родительский класс, который наследуется несколькими детьми. Я хотел бы инициализировать одного из потомков, используя инициализаторы @classmethod родителя. Как я могу это сделать? Я пытался:

class Point(object):
    def __init__(self,x,y):
        self.x = x
        self.y = y

    @classmethod
    def from_mag_angle(cls,mag,angle):
        x = mag*cos(angle)
        y = mag*sin(angle)
        return cls(x=x,y=y)


class PointOnUnitCircle(Point):
    def __init__(self,angle):
        Point.from_mag_angle(mag=1,angle=angle)


p1 = Point(1,2)
p2 = Point.from_mag_angle(2,pi/2)
p3 = PointOnUnitCircle(pi/4)
p3.x #fail

person alex    schedule 10.11.2015    source источник
comment
@classmethod и self ?! Это действительно не сочетается.   -  person deceze♦    schedule 10.11.2015
comment
Я бы сказал, что вся эта смесь выглядит довольно подозрительно и не может быть квалифицирована как хороший объектно-ориентированный код на любом языке.   -  person deceze♦    schedule 10.11.2015
comment
Первый параметр метода класса обычно называется cls, чтобы отличать его от экземпляра self. Вы, конечно, можете получить доступ к методам класса через self, но в данном случае непонятно, зачем вам это нужно, поскольку метод класса устанавливает атрибут класса.   -  person jonrsharpe    schedule 10.11.2015
comment
извините за ошибки, я их исправил.   -  person alex    schedule 10.11.2015
comment
Вы не можете назначить self в __init__ и ожидать, что это сработает. Если методы класса являются альтернативными конструкторами, почему бы просто не использовать A10.from_half_a(5)?   -  person jonrsharpe    schedule 10.11.2015
comment
Чего вы пытаетесь достичь? Вы читали о шаблоне проектирования фабрики?   -  person Raydel Miranda    schedule 10.11.2015
comment
1. @classmethod не является инициализатором. [Отредактировано для удаления точек, которые уже были исправлены, когда я набирал это.] 4. Назначение self в инициализаторе ничего не даст, особенно если это последняя строка __init__. ... Что, черт возьми, вы пытаетесь делать? В частности, как вы хотите, чтобы инициализированный дочерний экземпляр выглядел, и почему ни один из обычных способов сделать это не сработает для вас?   -  person Kevin J. Chase    schedule 10.11.2015
comment
@ KevinJ.Chase, я изменил синтаксис, чтобы он был правильным, что должно ответить на некоторые из ваших вопросов. в основном, я бы инициализировал A10 (и ему подобные) с использованием метода его родительского класса. есть и другие аргументы для каждого класса init, которые я опустил для простоты, но, возможно, они вводят в заблуждение. также я все время использую @classmethods в качестве альтернативных конструкторов.   -  person alex    schedule 10.11.2015
comment
Я удалил пункты 2 и 3, которые вы уже исправили. Я второй вариант @jonrsharpe - весь смысл параметра cls в @classmethod состоит в том, чтобы вы могли вернуть экземпляр подкласса, поэтому нет необходимости вызывать его в методе __init__ подкласса. Он уже делает то, что вы хотите.   -  person Kevin J. Chase    schedule 10.11.2015
comment
Метод класса вызывает __init__, а не наоборот. Наследование уже делает то, что вы хотите, потому что вы использовали cls(...), а не A(...) в унаследованном методе.   -  person jonrsharpe    schedule 10.11.2015
comment
Полагаю, я не согласен. часто классы можно инициализировать разными способами. Я использую методы @class, такие как from_this и from_that, для создания альтернативных конструкторов. поэтому я пытаюсь просто использовать родительский альтернативный конструктор из дочернего класса.   -  person alex    schedule 10.11.2015
comment
@alex, это не вопрос мнения! Когда вы вызываете cls(...) изнутри метода класса, он вызывает __init__ (и / или __new__) для любого класса cls, создавая новый экземпляр. я пытаюсь просто использовать родительский альтернативный конструктор из дочернего класса, поэтому, снова, просто используйте ChildClass.inherited_class_method(...). См., Например, stackoverflow.com/q/1015592/3001761, stackoverflow.com/q/1216356/3001761, почему вы не можете назначить self.   -  person jonrsharpe    schedule 10.11.2015
comment
Конструктор не является инициализатором. Если бы это было так, у него была бы ссылка на уже созданный экземпляр. Поскольку from_half_a имеет ровно два параметра --- cls и half_a --- и ни один не является ссылкой на какой-то недавно созданный экземпляр, он не может быть инициализатором.   -  person Kevin J. Chase    schedule 10.11.2015
comment
так что, возможно, мое использование методов классов для реализации альтернативных форм инициализации является плохой практикой? какой метод предпочтительнее?   -  person alex    schedule 10.11.2015
comment
Метод класса - хороший способ реализовать альтернативные конструкторы. Почему бы просто не использовать методы экземпляра, если вы хотите инициализировать атрибуты экземпляра? Или вам может потребоваться изучить использование __new__. Не могли бы вы привести менее абстрактный пример того, чего вы на самом деле пытаетесь достичь? Обратите внимание, что вам нужно включать @username при ответе на другие комментарии.   -  person jonrsharpe    schedule 10.11.2015
comment
Этот альтернативный конструктор прекрасен, просто ваше ожидание его использования кажется странным. A10() должен нормально инициализировать класс, A10.from_half_a() будет его альтернативным конструктором. Точно так же, как и для его родительского класса! Зачем A10() вдруг использовать альтернативный конструктор A?   -  person deceze♦    schedule 10.11.2015
comment
Альтернативный инициализатор? Если вы уже создаете подклассы, у вас уже есть альтернативный инициализатор - он называется SubclassOfA.__init__. Кажется, это не тот ответ, который вам нужен, но на данный момент я понятия не имею, чего вы хотите. Вы намекнули, что происходит что-то гораздо более сложное, но не то, что это такое, и почему типичное использование __init__ или @classmethod не сработает для вас.   -  person Kevin J. Chase    schedule 10.11.2015
comment
Обычно на этом сайте не объясняются голоса за, но я хотел подчеркнуть, насколько радикально все изменило ваше редактирование, добавляющее конкретный вариант использования. Это началось с длинных разочаровывающих аргументов в комментариях о терминологии и намерениях, отчасти потому, что абстрактный пример class A был слишком простым, чтобы продемонстрировать вашу проблему. Как только вы показали нам, что вы на самом деле пытались сделать, это стал на удивление хорошим вопросом, на который менее чем через 10 минут был получен полезный (для вас и, вероятно, для других) ответ от @jonrsharpe.   -  person Kevin J. Chase    schedule 11.11.2015


Ответы (2)


Если вы попытаетесь написать __init__ таким образом, ваш PointOnUnitCircle будет иметь интерфейс, отличный от Point (так как он принимает angle, а не x, y) и, следовательно, не должен быть его подклассом. Как насчет чего-то вроде:

class PointOnUnitCircle(Point):

    def __init__(self, x, y):
        if not self._on_unit_circle(x, y):
            raise ValueError('({}, {}) not on unit circle'.format(x, y))
        super(PointOnUnitCircle, self).__init__(x, y)

    @staticmethod
    def _on_unit_circle(x, y):
        """Whether the point x, y lies on the unit circle."""
        raise NotImplementedError

    @classmethod
    def from_angle(cls, angle):
        return cls.from_mag_angle(1, angle)

    @classmethod
    def from_mag_angle(cls, mag, angle):  
        # note that switching these parameters would allow a default mag=1
        if mag != 1:
            raise ValueError('magnitude must be 1 for unit circle')
        return super(PointOnUnitCircle, cls).from_mag_angle(1, angle)

Это сохраняет интерфейс неизменным, добавляет логику для проверки входных данных подкласса (после того, как вы его написали!) И предоставляет новый метод класса для простого создания нового PointOnUnitCircle из angle. Скорее, чем

p3 = PointOnUnitCircle(pi/4)

ты должен написать

p3 = PointOnUnitCircle.from_angle(pi/4)
person jonrsharpe    schedule 10.11.2015

Вы можете переопределить метод __new__ подкласса для создания экземпляров из альтернативного конструктора суперкласса, как показано ниже.

import math


class Point(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def from_polar(cls, radius, angle):
        x = radius * math.cos(angle)
        y = radius * math.sin(angle)
        return cls(x, y)


class PointOnUnitCircle(Point):

    def __new__(cls, angle):
        point = Point.from_polar(1, angle)
        point.__class__ = cls
        return point

    def __init__(self, angle):
        pass

Обратите внимание, что в __new__ строку point = Point.from_polar(1, angle) нельзя заменить на point = super().from_polar(1, angle), потому что, в то время как Point отправляет себя в качестве первого аргумента альтернативного конструктора, super() отправляет подкласс PointOnUnitCircle альтернативному конструктору, который циклически вызывает __new__ подкласса, который его вызывает, и т. Д. пока не появится RecursionError. Также обратите внимание, что даже если __init__ пуст в подклассе, без переопределения __init__ в подклассе, __init__ суперкласса будет автоматически вызываться сразу после __new__, отменяя альтернативный конструктор.

В качестве альтернативы, некоторые конструкции объектов проще с композицией, чем с наследованием. Например, вы можете заменить указанный выше класс PointOnUnitCircle, не переопределяя __new__, следующим классом.

class UnitCircle:

    def __init__(self, angle):
        self.set_point(angle)

    def set_point(self, angle):
        self.point = Point.from_polar(1, angle)
person Victor    schedule 14.01.2016
comment
Это как раз те проблемы, которые обсуждаются в моем ответе. Ваш подкласс теперь имеет совершенно другую сигнатуру по сравнению с родительским, и если вы вызовете from_polar на нем напрямую, произойдут плохие вещи. Вы не можете просто использовать его там, где вы использовали родителя. Насколько неудобна реализация, наводит на мысль о том, что это плохая идея. - person jonrsharpe; 14.01.2016
comment
Хорошая точка зрения. Мне пришлось бы превратить from_polar в статический метод, чтобы обойти эту проблему, и это могло бы привести к большему количеству проблем. Отказ от совместимости с super и переход к __new__ и __class__ действительно кажутся красными флажками. Я рад, что вы указали на недостаток. Если подкласс имеет смысл только с альтернативным конструктором суперкласса, кажется, лучше использовать композицию, потому что даже с предложенным вами решением без морщин обычное построение из подкласса больше не полезно, и в этом смысле, как вы говорите, вы не можете просто используйте его везде, где вы использовали родителя. - person Victor; 14.01.2016