views:

104

answers:

4

Hi

Level: Beginner

I'm doing my first steps in Object Oriented programming. The code is aimed at showing how methods are passed up the chain. So when i call UG.say(person, 'but i like') the method say is instructed to call class MITPerson. Given that MITPerson does not contain a say method it will pass it up to class Person. I think there is nothing wrong with the code as it is part of a lecture (see source below). I think it is me who is omitting to define something when i run the code. Not sure what though. I think that the UG instance the error message is looking for as first argument is refering to self but that, in principle, doesn't need to be provided, correct? Any hints?

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)

source: MIT OpenCourseWare http://ocw.mit.edu Introduction to Computer Science and Programming Fall 2008

+3  A: 

Change

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

to

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

UG.say returns the unbound method say. "Unbound" implies that the first argument to say is not automatically filled in for you. The unbound method say takes 3 arguments, and the first one must be an instance of UG. Instead, UG.say(person, 'but i like') sends an instance of Person as the first argument. This explains the error message Python gives to you.

In contrast, ug.say returns the bound method say. "Bound" implies that the first argument to say will be ug. The bound method takes 2 arguments, toWhom and something. Thus, ug.say(person, 'but i like') works as expected.

The concept of unbound method has been removed from Python3. Instead, UG.say just returns a function which (still) expects 3 arguments. The only difference is that there is no more type checking on the first argument. You'll still end up with an error, however, just a different one:

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

PS. When beginning to learn Python, I think I'd just try to accept that UG.say returns an unbound method (expecting 3 arguments), and ug.say is the normal proper way to call a method (expecting 2 arguments). Later, to really learn how Python implements this difference of behavior (while maintaining the same qualified name syntax), you'll want to research descriptors and the rules of attribute lookup.

unutbu
@all: thank you very much for you explanations! this was also a good example that shows how easy it is to get confused by names (be it variables, classes etc)...especially if the fundamentals are still a bit shaky...i'll try to be careful/attentive about naming conventions.
Baba
You're welcome, Baba. Best of luck with your studies.
unutbu
+4  A: 

You are calling class instead of instance.

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


UG.say(person, 'bla')

Call instance instead

>>> ug = UG('Dylan', 'Bob')
>>> ug.say(person, 'but i like')
Almad
+3  A: 

OK, time for a short tutorial on Python methods.

When you define a functions inside a class:

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

and then retrieve that function using a dotted lookup, you get a special object back called a "bound method", in which the first argument is automatically passed through to the function as the instance upon which it is called. See:

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

But, since that function was also defined on the class, you can look it up through the class instead of through a specific instance. If you do that, though, you won't get a bound method (obviously -- there's nothing to bind!). You'll get the original function, to which you need to pass the instance that you want to call it on:

>>> 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.

The first call fails, since the function say expects an instance of UG as its first argument and gets nothing. The second call automatically binds the first argument, so it works; the third manually passes the instance you want to use. The second and third are equivalent.


There's one more thing to mention, which is that it doesn't seem like the say function actually needs the instance of UG which is doing the saying. If not, you can register it as a "static method", which tells Python not to bind the first attribute:

>>> class UG:
...     @staticmethod
...     def say():
...             print("foo")
...
>>> ug = UG()
>>> UG.say()
foo
>>> ug.say()
foo
katrielalex
+4  A: 

The answers are quite fine, but there's a side note I think is important to make. Take the snippet (in class MITPerson):

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

This code is completely useless and redundant. When a subclass does not need to override anything in its superclass's implementation of a method, it's totally useless for the subclass to look like it's "overriding" that method... and then just delegate all the work, without any changes, to the superclass anyway.

Code which has absolutely no purpose, can never make any difference (except marginally slowing down the whole system), and can therefore be removed without any harm, should be removed: why have it there at all?! Any code that's present in your program, but not at all useful, is inevitably hurting the quality of your program: such useless "ballast" dilutes the useful, working code, making your program harder to read, maintain, debug, and so on.

Most people seem to grasp this intuitively in most situations (so you don't see a lot of code around which "fake-overrides" most methods but in the method body just punts to the superclass implementation) except for __init__ -- where many, for some reason, seem to have a mental blind spot and just can't see that exactly the same rule applies as for other methods. This blind spot may come from being familiar with other, completely different languages, where the rule does not apply to a class's constructor, plus a misconception that sees __init__ as a constructor where it actually isn't (it's an initializer).

So, to summarize: a subclass should define __init__ if, and only if, it needs to do something else before, or after, or both before and after, the superclass's own initializer (very rarely it may want to do something instead, i.e., not delegate to the superclass initializer at all, but that's hardly ever good practice). If the subclass's __init__ body has just a call to the superclass's __init__, with exactly the same parameters in the same order, expunge the subclass's __init__ from your code (just as you would do for any other similarly-redundant method).

Alex Martelli