Calling Methods on Objects

Introduction

Methods can be described as “functions called on objects.” In this lesson, you’ll learn the specific ways in which methods differ from functions, and the role that inheritance plays in the method call stack.

Add a Method

The child class can also add a method that was not present in its parent class. Going back to classes Car and Yugo, we’ll define the new method need_a_push() for class Yugo only:

>>> class Car():
...     def exclaim(self):
...         print("I'm a Car!")
...
>>> class Yugo(Car):
...     def exclaim(self):
...         print("I'm a Yugo! Much like a Car, but more Yugo-ish.")
...     def need_a_push(self):
...         print("A little help here?")
...

Next, make a Car and a Yugo:

>>> give_me_a_car = Car()
>>> give_me_a_yugo = Yugo()

A Yugo object can react to a need_a_push() method call:

>>> give_me_a_yugo.need_a_push()
A little help here?

But a generic Car object cannot:

>>> give_me_a_car.need_a_push()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Car' object has no attribute 'need_a_push'

At this point, a Yugo can do something that a Car cannot, and the distinct personality of a Yugo can emerge.

Get Help from Your Parent with super

We saw how the child class could add or override a method from the parent. What if it wanted to call that parent method? “I’m glad you asked,” says super(). We’ll define a new class called EmailPerson that represents a Person with an email address. First, our familiar Person definition:

>>> class Person():
...     def __init__(self, name):
...         self.name = name
...

Notice that the __init__() call in the following subclass has an additional email parameter:

>>> class EmailPerson(Person):
...     def __init__(self, name, email):
...         super().__init__(name)
...         self.email = email

When you define an __init__() method for your class, you’re replacing the __init__() method of its parent class, and the latter is not called automatically anymore. As a result, we need to call it explicitly. Here’s what’s happening:

  • The super() gets the definition of the parent class, Person.
  • The __init__() method calls the Person.__init__() method. It takes care of passing the self argument to the superclass, so you just need to give it any optional arguments. In our case, the only other argument Person() accepts is name.
  • The self.email = email line is the new code that makes this EmailPerson different from a Person.

Moving on, let’s make one of these creatures:

>>> bob = EmailPerson('Bob Frapples', 'bob@frapples.com')

We should be able to access both the name and email attributes:

>>> bob.name
'Bob Frapples'
>>> bob.email
'bob@frapples.com'

Why didn’t we just define our new class as follows?

>>> class EmailPerson(Person):
...     def __init__(self, name, email):
...         self.name = name
...         self.email = email

We could have done that, but it would have defeated our use of inheritance. We used super() to make Person do its work, the same as a plain Person object would. There’s another benefit: if the definition of Person changes in the future, using super() will ensure that the attributes and methods that EmailPerson inherits from Person will reflect the change.

Use super() when the child is doing something its own way but still needs something from the parent (as in real life).

In self Defense

One criticism of Python (besides the use of whitespace) is the need to include self as the first argument to instance methods (the kind of method you’ve seen in the previous examples). Python uses the self argument to find the right object’s attributes and methods. For an example, I’ll show how you would call an object’s method, and what Python actually does behind the scenes.

Remember class Car from earlier examples? Let’s call its exclaim() method again:

>>> car = Car()
>>> car.exclaim()
I'm a Car!

Here’s what Python actually does, under the hood:

  • Look up the class (Car) of the object car.
  • Pass the object car to the exclaim() method of the Car class as the self parameter.

Just for fun, you can even run it this way yourself and it will work the same as the normal (car.exclaim()) syntax:

>>> Car.exclaim(car)
I'm a Car!

However, there’s never a reason to use that lengthier style.

Get and Set Attribute Values with Properties

Some object-oriented languages support private object attributes that can’t be accessed directly from the outside; programmers often need to write getter and setter methods to read and write the values of such private attributes.

Python doesn’t need getters or setters, because all attributes and methods are public, and you’re expected to behave yourself. If direct access to attributes makes you nervous, you can certainly write getters and setters. But be Pythonic—use properties.

In this example, we’ll define a Duck class with a single attribute called hidden_name. (In the next section, I’ll show you a better way to name attributes that you want to keep private.) We don’t want people to access this directly, so we’ll define two methods: a getter (get_name()) and a setter (set_name()). I’ve added a print() statement to each method to show when it’s being called. Finally, we define these methods as properties of the name attribute:

>>> class Duck():
...     def __init__(self, input_name):
...         self.hidden_name = input_name
...     def get_name(self):
...         print('inside the getter')
...         return self.hidden_name
...     def set_name(self, input_name):
...         print('inside the setter')
...         self.hidden_name = input_name
...     name = property(get_name, set_name)

The new methods act as normal getters and setters until that last line; it defines the two methods as properties of the attribute called name. The first argument to property() is the getter method, and the second is the setter. Now, when you refer to the name of any Duck object, it actually calls the get_name() method to return it:

>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'

You can still call get_name() directly, too, like a normal getter method:

>>> fowl.get_name()
inside the getter
'Howard'

When you assign a value to the name attribute, the set_name() method will be called:

>>> fowl.name = 'Daffy'
inside the setter
>>> fowl.name
inside the getter
'Daffy'

You can still call the set_name() method directly:

>>> fowl.set_name('Daffy')
inside the setter
>>> fowl.name
inside the getter
'Daffy'

Another way to define properties is with decorators. In this next example, we’ll define two different methods, each called name() but preceded by different decorators:

  • @property, which goes before the getter method
  • @name.setter, which goes before the setter method

Here’s how they actually look in the code:

>>> class Duck():
...     def __init__(self, input_name):
...         self.hidden_name = input_name
...     @property
...     def name(self):
...         print('inside the getter')
...         return self.hidden_name
...     @name.setter
...     def name(self, input_name):
...         print('inside the setter')
...         self.hidden_name = input_name

You can still access name as though it were an attribute, but there are no visible get_name() or set_name() methods:

>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'

Note

If anyone guessed that we called our attribute hidden_name, they could still read and write it directly as fowl.hidden_name. In the next section, you’ll see how Python provides a special way to name private attributes.

In both of the previous examples, we used the name property to refer to a single attribute (ours was called hidden_name) stored within the object. A property can refer to a computed value, as well. Let’s define a Circle class that has a radius attribute and a computed diameter property:

>>> class Circle():
...     def __init__(self, radius):
...         self.radius = radius
...     @property
...     def diameter(self):
...         return 2 * self.radius
...

We create a Circle object with an initial value for its radius:

>>> c = Circle(5)
>>> c.radius
5

We can refer to diameter as if it were an attribute such as radius:

>>> c.diameter
10

Here’s the fun part: we can change the radius attribute at any time, and the diameter property will be computed from the current value of radius:

>>> c.radius = 7
>>> c.diameter
14

If you don’t specify a setter property for an attribute, you can’t set it from the outside. This is handy for read-only attributes:

>>> c.diameter = 20
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

There’s one more big advantage of using a property over direct attribute access: if you ever change the definition of the attribute, you only need to fix the code within the class definition, not in all the callers.

Name Mangling for Privacy

In the Duck class example in the previous section, we called our (not completely) hidden attribute hidden_name. Python has a naming convention for attributes that should not be visible outside of their class definition: begin by using with two underscores (__).

Let’s rename hidden_name to __name, as demonstrated here:

>>> class Duck():
...     def __init__(self, input_name):
...         self.__name = input_name
...     @property
...     def name(self):
...         print('inside the getter')
...         return self.__name
...     @name.setter
...     def name(self, input_name):
...         print('inside the setter')
...         self.__name = input_name
...

Take a moment to see if everything still works:

>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'

Looks good. And, you can’t access the __name attribute:

>>> fowl.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Duck' object has no attribute '__name'

This naming convention doesn’t make it private, but Python does mangle the name to make it unlikely for external code to stumble upon it. If you’re curious and promise not to tell everyone, here’s what it becomes:

>>> fowl._Duck__name
'Donald'

Notice that it didn’t print inside the getter. Although this isn’t perfect protection, name mangling discourages accidental or intentional direct access to the attribute.

Method Types

Some data (attributes) and functions (methods) are part of the class itself, and some are part of the objects that are created from that class.

When you see an initial self argument in methods within a class definition, it’s an instance method. These are the types of methods that you would normally write when creating your own classes. The first parameter of an instance method is self, and Python passes the object to the method when you call it.

In contrast, a class method affects the class as a whole. Any change you make to the class affects all of its objects. Within a class definition, a preceding @classmethod decorator indicates that that following function is a class method. Also, the first parameter to the method is the class itself. The Python tradition is to call the parameter cls, because class is a reserved word and can’t be used here. Let’s define a class method for A that counts how many object instances have been made from it:

>>> class A():
...     count = 0
...     def __init__(self):
...         A.count += 1
...     def exclaim(self):
...         print("I'm an A!")
...     @classmethod
...     def kids(cls):
...         print("A has", cls.count, "little objects.")
...
>>>
>>> easy_a = A()
>>> breezy_a = A()
>>> wheezy_a = A()
>>> A.kids()
A has 3 little objects.

Notice that we referred to A.count (the class attribute) rather than self.count (which would be an object instance attribute). In the kids() method, we used cls.count, but we could just as well have used A.count.

A third type of method in a class definition affects neither the class nor its objects; it’s just in there for convenience instead of floating around on its own. It’s a static method, preceded by a @staticmethod decorator, with no initial self or class parameter. Here’s an example that serves as a commercial for the class CoyoteWeapon:

>>> class CoyoteWeapon():
...     @staticmethod
...     def commercial():
...         print('This CoyoteWeapon has been brought to you by Acme')
...
>>>
>>> CoyoteWeapon.commercial()
This CoyoteWeapon has been brought to you by Acme

Notice that we didn’t need to create an object from class CoyoteWeapon to access this method. Very class-y.

6. Oh Oh: Objects and Classes is from the book:

Minimise

Unlock the rest of this chapter from Introducing Python and 30,000 other books and videos

Unlock the rest of this book
Learn about Safari for Business