Основы объектно-ориентированного программирования: наследование и теневое копирование (Python) - PullRequest
1 голос
/ 26 сентября 2010

Уровень: Начинающий

Я делаю свои первые шаги в объектно-ориентированном программировании. Код призван показать, как методы передаются по цепочке. Поэтому, когда я вызываю UG.say(person, 'but i like'), метод say получает указание вызвать класс MITPerson. Учитывая, что MITPerson не содержит say метод, он передаст его классу Person. Я думаю, что в коде нет ничего плохого, поскольку он является частью лекции (см. Источник ниже). Я думаю, что я не могу определить что-то, когда я запускаю код. Не уверен, что, хотя. Я думаю, что UG instance сообщение об ошибке ищет, поскольку первый аргумент ссылается на self, но это, в принципе, не требуется, верно? Есть намеки?

class Person(object):
    def __init__(self, family_name, first_name):
        self.family_name = family_name
        self.first_name = first_name
    def familyName(self):
        return self.family_name
    def firstName(self):
        return self.first_name
    def say(self,toWhom,something):
        return self.first_name + ' ' + self.family_name + ' says to ' +   toWhom.firstName() + ' ' + toWhom.familyName() + ': ' + something


class MITPerson(Person):
    def __init__(self, familyName, firstName):
        Person.__init__(self, familyName, firstName)


class UG(MITPerson):
    def __init__(self, familyName, firstName):
        MITPerson.__init__(self, familyName, firstName)
        self.year = None
    def say(self,toWhom,something):
        return MITPerson.say(self,toWhom,'Excuse me, but ' + something)



>>> person = Person('Jon', 'Doe')
>>> person_mit = MITPerson('Quin', 'Eil')
>>> ug = UG('Dylan', 'Bob')
>>> UG.say(person, 'but i like')


    UG.say(person, 'bla')
**EDIT (for completeness)**: it should say UG.say(person, 'but i like') #the 'bla' creeped in from a previous test
TypeError: unbound method say() must be called with UG instance as first argument (got Person instance instead)

источник: MIT OpenCourseWare http://ocw.mit.edu Введение в информатику и программирование, осень 2008

Ответы [ 4 ]

4 голосов
/ 26 сентября 2010

Вы вызываете класс вместо экземпляра.

>>> ug = UG('Dylan', 'Bob')
>>> UG.say(person, 'but i like')


UG.say(person, 'bla')

Вместо этого вызовите экземпляр

>>> ug = UG('Dylan', 'Bob')
>>> ug.say(person, 'but i like')
3 голосов
/ 26 сентября 2010

Ответы вполне хороши, но есть примечание, которое я считаю важным сделать.Возьмите фрагмент (в классе MITPerson):

def __init__(self, familyName, firstName):
    Person.__init__(self, familyName, firstName)

Этот код совершенно бесполезен и избыточен.Когда подклассу не нужно переопределять что-либо в реализации метода своего суперкласса, для подкласса совершенно бесполезно выглядеть, как будто он "переопределяет" этот метод ... и затем просто делегировать всю работу, безлюбые изменения в суперклассе в любом случае.

Код, который не имеет абсолютно никакой цели, никогда не будет иметь никакого значения (кроме незначительного замедления всей системы), и поэтому может быть удален без какого-либо вреда, должен быть удаленным: зачем это вообще ?!Любой код, который присутствует в вашей программе, но совсем не полезен, неизбежно ухудшает качество вашей программы: такой бесполезный «балласт» разбавляет полезный рабочий код, делая вашу программу труднее читать, поддерживать, отлаживать и т. Д.

Большинство людей, кажется, понимают это интуитивно в большинстве ситуаций (поэтому вы не видите большого количества кода, вокруг которого "фальсифицирует-переопределяет" большинство методов, но в теле метода просто указывается реализация суперкласса) за исключением для __init__ - где многие по какой-то причине, кажется, имеют умственную слепую зону и просто не могут видеть, что применяется точно такое же правило, как и для других методов.Это слепое пятно может возникнуть из-за того, что вы знакомы с другими, совершенно другими языками, где правило не применяется к конструктору класса , плюс ошибочное представление, что __init__ воспринимается как конструктор, в действительности его нет (это инициализатор ).

Итак, подведем итог: подкласс должен определять __init__ тогда и только тогда, когда ему нужно сделать что-то еще до, или после, или оба до ипосле этого, собственный инициализатор суперкласса (очень редко он может захотеть сделать что-то вместо , то есть не делегировать инициализатору суперкласса вообще, но это вряд ли когда-либо будет хорошей практикой).Если тело __init__ подкласса имеет просто вызов к __init__ суперкласса с точно такими же параметрами в том же порядке, исключите из своего кода __init__ подкласса (как вы это сделали бы длялюбой другой аналогично-избыточный метод).

2 голосов
/ 26 сентября 2010

ОК, время для короткого руководства по методам Python.

Когда вы определяете функции внутри класса:

>>> class MITPerson:
...     def say(self):
...             print ("I am an MIT person.")
...
>>> class UG(MITPerson):
...     def say(self):
...             print ("I am an MIT undergrad.")
...

и затем извлекая эту функцию, используя точечный поиск, вы получаете специальный объект, называемый «связанным методом», в котором первый аргумент автоматически передается функции как экземпляру, для которого она вызывается. См:

>>> ug = UG()
>>> ug.say
<bound method UG.say of <__main__.UG object at 0x022359D0>>

Но, поскольку эта функция также была определена в классе, вы можете просматривать ее через класс, а не через конкретный экземпляр. Если вы сделаете это, вы не получите связанный метод (очевидно - связывать нечего!). Вы получите оригинальную функцию, в которую вам нужно передать экземпляр, для которого вы хотите вызвать его:

>>> UG.say()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method say() must be called with UG instance as first argument (got nothing instead)
>>> ug.say()
I am an MIT undergrad.
>>> UG.say(ug)
I am an MIT undergrad.

Первый вызов завершается неудачно, поскольку функция say ожидает экземпляр UG в качестве первого аргумента и ничего не получает. Второй вызов автоматически связывает первый аргумент, поэтому он работает; третий вручную передает экземпляр, который вы хотите использовать. Второе и третье эквивалентны.


Есть еще одна вещь, которую стоит упомянуть, это то, что не похоже, что для функции say действительно нужен экземпляр UG, который делает поговорку. Если нет, вы можете зарегистрировать его как «статический метод», который говорит Python не связывать первый атрибут:

>>> class UG:
...     @staticmethod
...     def say():
...             print("foo")
...
>>> ug = UG()
>>> UG.say()
foo
>>> ug.say()
foo
2 голосов
/ 26 сентября 2010

Изменение

UG.say(person, 'but i like')

до

ug.say(person, 'but i like')

UG.say возвращает несвязанный метод say. «Unbound» подразумевает, что первый аргумент say не заполняется автоматически. Несвязанный метод say принимает 3 аргумента, и первый должен быть экземпляром UG. Вместо, UG.say(person, 'but i like') отправляет экземпляр Person в качестве первого аргумента. Это объясняет сообщение об ошибке, которое дает вам Python.

Напротив, ug.say возвращает связанный метод say. «Связанный» подразумевает, что первым аргументом будет ug. Связанный метод принимает 2 аргумента, toWhom и something. Таким образом, ug.say(person, 'but i like') работает как положено.

Концепция несвязанного метода была удалена из Python3 . Вместо этого UG.say просто возвращает функцию, которая (все еще) ожидает 3 аргумента. Разница лишь в том, что больше нет проверки типов по первому аргументу. Однако вы все равно получите ошибку, просто другую:

TypeError: say() takes exactly 3 positional arguments (2 given)

PS. Когда я начинаю изучать Python, я думаю, что я просто попытаюсь принять, что UG.say возвращает несвязанный метод (ожидается 3 аргумента), а ug.say - нормальный правильный способ вызова метода (ожидание 2 аргументов). Позже, чтобы по-настоящему узнать, как Python реализует эту разницу в поведении (при сохранении того же синтаксиса квалифицированных имен), вы захотите исследовать дескрипторы и правила поиска атрибутов .

...