Search code examples
pythonclasspython-2.7methodsattributeerror

Why do I get an AttributeError on a class, even though I defined a method with that name?


I'm taking an "intro to object-oriented programming" class and I'm on chapter 17 of Think Python. The following code is stumping me. I keep getting "AttributeError: 'Time' object has no attribute 'print_time'" when running it and I'm having little luck figuring it out. Can someone help break down what I'm doing wrong and help explain it a little better for me?

class Time(object):
    """represents the time of day.  attributes: hour, minute, second"""

def print_time(time):
    print '%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second)

start = Time()

start.hour = 9
start.minute = 45
start.second = 00

print_time(start)

class Time(object):
    def print_time(time):
        print '%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second)

start.print_time()

I read that it was an indentation error but I have IDLE set to warn me for conflicts and have checked it twice to no avail.


Solution

  • Let's go through your code a step at a (cough) time.

    class Time(object):
        """represents the time of day.  attributes: hour, minute, second"""
    

    Here, you're defining a class Time. It has a docstring, but no methods or other attributes.

    def print_time(time):
        print '%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second)
    

    This is a function, completely separate from the Time class you defined earlier, which accepts an argument time and tries to print a string using time's hour, minute and second attributes. If time doesn't have all of those attributes, you'll get an error.

    start = Time()
    

    This creates a Time object called start.

    start.hour = 9
    start.minute = 45
    start.second = 00
    

    These lines add hour, minute and second attributes to start.

    print_time(start)
    

    This calls print_time with start as an argument, resulting in the output:

    09:45:00
    

    So far so good. Now ...

    class Time(object):
        def print_time(time):
            print '%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second)
    

    This creates a new class, which happens to also be called Time. It doesn't have a docstring this time, but it does have a method called print_time (which is completely separate from the print_time function defined earlier, although it has the same name and code).

    It's important to realise at this point that just because you've created a new class called Time, that doesn't have any effect at all on objects you created with the previously defined class. They're still instances of the original Time class you defined. It's easy to prove this with the builtin help function:

    >>> help(start)
    Help on Time in module __main__ object:
    
    class Time(__builtin__.object)
     |  represents the time of day.  attributes: hour, minute, second
     |  
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
    start.print_time()
    

    Notice that help shows you the docstring from the original Time class, but not the method from the new version.

    >>> help(Time)
    Help on class Time in module __main__:
    
    class Time(__builtin__.object)
     |  Methods defined here:
     |  
     |  print_time(time)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
    

    ... whereas this shows the print_time method from the new Time class, but not the docstring from the original one.

    One way to think of this is that a class is like a factory that makes objects – and tearing down the Ford factory then building a new one in the same place doesn't have any effect on the '71 Mustang already sitting in your driveway.

    If you want an instance of the new Time class, you'll have to create it:

    >>> restart = Time()
    

    ... and then, once it has the right attributes ...

    >>> restart.hour = 9
    >>> restart.minute = 45
    >>> restart.second = 00
    

    ... you'll be able to call its print_time method successfully:

    >>> restart.print_time()
    09:45:00