Python: как ссылаться на переменные-члены

Я изучал Python на Codecademy, и я совершенно сбит с толку. Я не могу понять, как ссылаться на переменные-члены (надеюсь, так они называются). Вот фрагмент кода, который я написал, чтобы продемонстрировать свою путаницу:

class Triangle(object):
    number_of_sides = 3

    def __init__(self, angle1, angle2, angle3):
        self.angle1 = angle1
        self.angle2 = angle2
        self.angle3 = angle3

    def check_angles(self):
        return self.angle1 + self.angle2 + self.angle3 == 180

class Equilateral(Triangle):
    angle = 60
    def __init__(self):
        self.angle1 = self.angle
        self.angle2 = self.angle
        self.angle3 = self.angle

Так в равностороннем подклассе, angle1, angle2, angle3, не включены в качестве параметров __init__, Однако в приведенном ниже коде __init__ повторно инициализирует model, color, а также mpg, Почему это? Разве он не должен просто наследоваться, как в приведенном выше коде с Equilateral подкласс? Я не понимаю, почему они были написаны по-другому.

class Car(object):
    condition = "new"
    def __init__(self, model, color, mpg):
        self.model = model
        self.color = color
        self.mpg   = mpg

    def display_car(self):
        print "This is a %s %s with %s MPG." %(self.color, self.model, str(self.mpg))

    def drive_car(self):
        self.condition = "used"

class ElectricCar(Car):
    def __init__(self, model, color, mpg, battery_type):
        self.model = model
        self.color = color
        self.mpg   = mpg
        self.battery_type = battery_type

3 ответа

Решение

Однако в приведенном ниже коде init повторно инициализирует модель, цвет и mpg. Почему это?

Потому что автор ElectricCar хочет, чтобы пользователь мог инициализировать ElectricCar с четырьмя параметрами.

ec = ElectricCar('xyz-500', 'blue', 0.5, 'boxy')

Однако они должны были передать аргументы базовому классу __init__ метод:

class ElectricCar(Car):
    def __init__(self, model, color, mpg, battery_type):
        super(ElectricCar, self).__init__(model, color, mpg)
        self.battery_type = battery_type

В случае EquilateralTriangleвсе углы одинаковы и должны составлять 60 градусов, поэтому нет смысла инициализировать такой объект с трех предоставленных пользователем углов.

Тот же комментарий о базовом классе __init__ применяется:

class Equilateral(Triangle):
     angle = 60
     def __init__(self):
         super(Equilateral, self).__init__(Equilateral.angle,
                                           Equilateral.angle,
                                           Equilateral.angle)

Также обратите внимание, что нет смысла инициализировать Triangle с трех сторон, если вы говорите о виде пространства, в котором внутренние углы треугольника составляют до 180 градусов (или любое другое фиксированное число). Было бы разумнее пропустить только два угла.

Обе реализации, кажется, немного не в порядке. В Python суперкласс __init__() не вызывается автоматически. Вы должны сделать это явно.

Разве он не должен наследоваться так же, как в вышеприведенном коде с подклассом Equilib?

Когда экземпляр Equilateral создано, Triangle.__init__() никогда не вызывается в приведенной выше реализации. Не существует автоматического наследования инициализатора (это нарушит PEP 20: "Явное лучше, чем неявное").

Equilateral наверное стоит лучше прочитать

class Equilateral(Triangle):
     angle = 60
     def __init__(self):
         super(Equilateral, self).__init__(self.angle, self.angle, self.angle)

То же самое с ElectricCar:

class ElectricCar(Car):
    def __init__(self, model, color, mpg, battery_type):
        super(ElectricCar, self).__init__(model, color, mpg)
        self.battery_type = battery_type

Я не понимаю, почему они были написаны по-другому.

На этот вопрос сложно ответить. Автор либо неправильно понял, как работает наследование Python, либо у него была явная причина не вызывать инициализатор суперкласса.

В Python классы работают немного как лук. Самым внешним слоем является объект 'instance', self, Слой ниже - это объект класса, экземпляром которого является self. Слои ниже, которые являются объектами класса, от которых унаследованы (базовые классы).

Положить что-то в определение класса, например number_of_sides в Triangle, помещает что-то в объект класса. Назначение self помещает что-то в экземпляр объекта, другой слой лука.

При разрешении имени как self.anglePython начинает смотреть на self слой. Если его там нет, он смотрит на слой ниже и так далее. В эквилаторальном примере angle не найден в self, но он найден в Equilatoral учебный класс. angle1 известен только в переменной экземпляра self, не в переменной класса Equilatoral,

В примере автомобиля переменные model, color а также mpg хранятся в экземпляре, а не в самом классе. Они создаются, когда функция Car.__init__ называется, не наследуется каким-то магическим кодом. Это сделано специально, так как Python предпочитает явное, а не неявное поведение. Как объяснил juan, вы должны явно вызвать конструктор базового класса, чтобы полностью инициализировать объект экземпляра.

Сначала необходимость явного вызова базовых конструкторов кажется обременительной, но в языках, которые предпочитают неявные действия (такие как C++ и его производные), вам нужны всевозможные синтаксические кошмары для явного переопределения неявного поведения. Это одна из причин, почему у Python такая мягкая кривая обучения.

Другие вопросы по тегам