Getters/Setters/Fuxors

This is the second article following up Phillip J. Eby’s Python Is Not Java. In the first article, The Static Method Thing, we took a look at how Java static methods differ from Python class/static methods. This time we’re going to dive deep into the evils of getters and setters.

I’m hopping around a bit because this was the item that I saw the most confusion around in comments and trackback posts. I’m also a bit worried that some walked away with the impression that getters and setters are always evil and should never be used, ever, in any language, ever. Getters and setters are not GOTOs, and Phillip’s original post never came close to making that claim.

Let’s take a look at what Phillip had to say about getters and setter use in Python:

Getters and setters are evil. Evil, evil, I say! Python objects are not Java beans. Do not write getters and setters. This is what the ‘property’ built-in is for. And do not take that to mean that you should write getters and setters, and then wrap them in ‘property’. That means that until you prove that you need anything more than a simple attribute access, don’t write getters and setters. They are a waste of CPU time, but more important, they are a waste of programmer time. Not just for the people writing the code and tests, but for the people who have to read and understand them as well.

In Java, you have to use getters and setters because using public fields gives you no opportunity to go back and change your mind later to using getters and setters. So in Java, you might as well get the chore out of the way up front. In Python, this is silly, because you can start with a normal attribute and change your mind at any time, without affecting any clients of the class. So, don’t write getters and setters.

I thought he explained this well. There’s not a lot missing here so I’m going to dive real deep and try to give a lot of background and code examples on the situation.

From the top

To start from the beginning, let’s take a look at what getters and setters look like in Java… Actually, no. Let’s back up even further and follow the trail to where the first realization of needing getters and setters might take place.

Here’s a nice simple class, Contact, with member variables for storing the kinds of information you might find in an Address Book application:

public class Contact {
    public String firstName;
    public String lastName;
    public String displayName;
    public String email;
    
    public C() {}

    public C(firstName, lastName, displayName, email){
        this.firstName = firstName;
        this.lastName = lastName;
        this.displayName = displayName;
        this.email = email;
    }

    public void printInfo() {
       System.out.println(this.displayName +
                          " <" + this.email + ">");
    }
}

We release version 1.0 of our Contact Management API and everyone rushes to use the wealth of functionality it provides in their own address book applications. Now there’s code everywhere that looks like this:

Contact contact = new Contact();
contact.firstName = "Phillip";
contact.lastName = "Eby";
contact.displayName = "Phillip J. Eby";
contact.email = "[email protected]";
contact.printInfo();

Later, the feature requests begin pouring in from excited developers around the world:

Now we’re screwed. We need a mechanism for attaching behavior to our member variables but that mechanism does not exist in Java. It was thus that the getter/setter convention was spat out onto an otherwise pure Java community.

The Getter/Setter Convention

The getter/setter pattern says that public member variables should be used only when you are absolutely positive that you will never need to attach behavior to member variable access. If you need behavior attached to your member variables, or you believe you will some time in the future, you should use the getter/setter convention when the class is initially designed:

  1. Make the member variable non-public. If you want to hide the member variable from subclasses and classes in the same package, forcing everyone to go through the get/set methods, use private. If you believe having direct access to the member variable from subclasses is good, use protected.

  2. Write get and set methods for the member variable.

Let’s take a look at what our Contact class would have looked like if it had been designed properly by using the getter/setter convention:

public class Contact {
    private String firstName;
    private String lastName;
    private String displayName;
    private String email;

    public C(){}

    public C(firstName, lastName, displayName, email){
        setFirstName(firstName);
        setLastName(lastName);
        setDisplayName(displayName);
        setEmail(email);
    }

    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String value) {
        firstName = value;
    }
    
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String value) {
        lastName = value;
    }
    
    public String getDisplayName() {
        return displayName;
    }
    public void setDisplayName(String value) {
        displayName = value;
    }
    
    public String getEmail() {
        return email;
    }
    public void setEmail(String value) {
        email = value;
    }
    
    public void printInfo() {
       System.out.println(this.getDisplayName() +
                          " <" + this.getEmail() + ">");
    }
}

The ability to now go back and add code to the get/set methods is the reason we use the getter/setter convention. If we had some mechanism for adding code later, we could just use public member variables.

The Brush and The Canvas

There are a few things worth calling out right now as this is where Python programmers reading this are going to start feeling a bit queasy:

  1. The number of lines have increased by almost 400%.
  2. The immediate functionality gain for those extra lines is zero.
  3. The getter/setter methods follow an obvious and boring pattern (perfect for a machine to deal with).

Here’s where I see a huge difference in mind-set between Python and Java coders… Practices like getter/setter that lead to code bloat are generally met with less resistance in the Java community because the intelligent IDEs and other tools go a long way in managing the excess code:

Who cares if the source file is 4000 lines, I have a nice tree-view over here showing me the ten method signatures and whatnot.

In Java, the tool (IDE) is the brush and the code is the canvas. In Python, the language is the brush and the machine is the canvas - the language and the tool are the same thing. In Java you enhance the IDE, in Python you enhance the language.

This is the primary reason why there is little serious demand for Python IDEs. Many people coming to Python can’t believe no one uses IDEs. The automatic assumption is that Python is for old grey beards who are comfortable with vi and Emacs and refuse to accept breakthroughs in programming productivity like IDEs. Then they write a little Python code and realize that an IDE would just get in their way.

Getter/Setter Convention in Python

Minor syntax differences aside, Python has the same getter/setter convention as Java. The difference is when it is applied. Let’s take a look at the Python implementation of the basic Contact class:

class Contact(object):

    def __init__(self, first_name=None, last_name=None, 
                 display_name=None, email=None):
        self.first_name = first_name
        self.last_name = last_name
        self.display_name = display_name
        self.email = email

    def print_info(self):
        print self.display_name, "<" + self.email + ">"            

And code to use it:

contact = Contact()
contact.first_name = "Phillip"
contact.last_name = "Eby"
contact.display_name = "Phillip J. Eby"
contact.email = "[email protected]"
contact.print_info()

Pretty much the same as the Java example before adding get/set methods.

Now we need to implement some logic that will raise an exception if an email address is set that doesn’t look like an email address:

class Contact(object):

    def __init__(self, first_name=None, last_name=None, 
                 display_name=None, email=None):
        self.first_name = first_name
        self.last_name = last_name
        self.display_name = display_name
        self.email = email

    def print_info(self):
        print self.display_name, "<" + self.email + ">"            

    def set_email(self, value):
        if '@' not in value:
            raise Exception("This doesn't look like an email address.")
        self._email = value

    def get_email(self):
        return self._email

    email = property(get_email, set_email)

What’s happened here is that we were able to add get/set methods and still maintain backward compatibility. The following code still runs properly:

contact = Contact()
contact.email = "[email protected]"

When the email attribute is set, the set_email method is called. When the email attribute is got, the get_email method is called.

The basic value to take away from all this is that you want to strive to make sure every single line of code has some value or meaning to the programmer. Programming languages are for humans, not machines. If you have code that looks like it doesn’t do anything useful, is hard to read, or seems tedious, then chances are good that Python has some language feature that will let you remove it.

What not to do

This article would not be complete without giving an example of what not to do in Python:

class Contact(object):

    def __init__(self, first_name=None, last_name=None, 
                 display_name=None, email=None):
        self.set_first_name(first_name)
        self.set_last_name(last_name)
        self.set_display_name(display_name)
        self.set_email(email)

    def set_first_name(self, value):
        self._first_name = value

    def get_first_name(self):
        return self._first_name

    def set_last_name(self, value):
        self._last_name = value

    def get_last_name(self):
        return self._last_name

    def set_display_name(self, value):
        self._display_name = value

    def get_display_name(self):
        return self._display_name

    def set_email(self, value):
        self._email = value

    def get_email(self):
        return self._email

    def print_info(self):
        print self.display_name, "<" + self.email + ">"            

Last Notes

Many of the other items Phillip mentioned are truly just differences in idiom. I hope my Python bias didn’t effect the quality of this article. My goal has been to attack the issues Phillip raised with as little bias as possible but this is an item where I believe Python has an approach that is obviously superior.