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