Why Returning a Mutable Attribute Might Be Dangerous

Avoid Returning Mutable Attribute

People who come from C++, Java or C# often provide getter/setter to provide access to their classes’ internals. In Python, there is not point to use getter/setter unless there is a good reason for doing so. I have been coding in Python for several years and have not yet come by one such reason.

I have seen code where people prefix a class instance’s attribute with underscore thinking it should make that attribute private. The following example will demonstrate that point:

class Car(object):
    def __init__(self, owners):
        self._owners = owners

    def get_owners(self):
        return self._owners


if __name__ == '__main__':
    car = Car(['Peter', 'Paul'])
    print 'This car belongs to', car.get_owners()

    owners = car.get_owners()
    owners.append('Mary')  
    print 'This car belongs to', car.get_owners()

Output:

This car belongs to ['Peter', 'Paul']
This car belongs to ['Peter', 'Paul', 'Mary']

In the example above, the user calls car.getowners() to gain access to a private member, but then went on modifying it. The immediate solution is to return a copy of self.owners. That way, changes made to the copy does not affect the original member.

Avoid Returning Attributes at All

Instead of allowing the users to gain access to your class’ internals, why not provide what the users really want. Think of a class attributes as nouns (things) and methods as verbs (actions). A class should provide the users with actions, not nouns.

Here is a typical example:

import fnmatch

class FileFilter(object):
    def __init__(self, include=None):
        self._include = include

    @property
    def include_patterns(self):
        return self._include[:]

# Usage example: prints all the files that fit a set of patterns
filter = FileFilter(include=['*.py', '*.ipynb'])
for filename in ['readme.txt', 'hello.py', 'greeting.ipynb']:
    for pattern in filter.include_patterns:
        if fnmatch.fnmatch(filename, pattern):
            print filename
            break

Output:

hello.py
greeting.ipynb

Provide Services Instead of Attributes

In the example above, the class FileFilter provides a getter, include which returns a list of pattern. Instead of providing the users with an attribute (self.include, a noun), it should instead provide a service, an action, namely: shouldinclude:

import fnmatch

class FileFilter(object):
    def __init__(self, include=None):
        self._include = include

    def should_include(self, filename):
        return any(fnmatch.fnmatch(filename, pattern) for pattern in self._include)

# Usage example: prints all the files that fit a set of patterns
filter = FileFilter(include=['*.py', '*.ipynb'])
for filename in ['readme.txt', 'hello.py', 'greeting.ipynb']:
    if filter.should_include(filename):
        print filename

Output:

hello.py
greeting.ipynb

Notice in the example above, FileFilter does not expose any internal data. Instead, it offers a service (an action, or verb, namely should_include). Also notice the code which uses the FileFilter class is also simpler, with one less nested level.

Conclusion

When designing a class, you should avoid returning a class’ internal data, especially mutable structures. Instead, you need to study the users code to see what they use those data for and offer services to accomplish their goals.

2 thoughts on “Why Returning a Mutable Attribute Might Be Dangerous

  1. Pablo

    In the second example, ‘continue’ should be replaced by ‘break’.

    Good post. Thank you.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s