Learning Python - Mark Lutz [354]
Notice that the argument names appear twice here. This code might seem a bit redundant at first, but it’s not. The job argument, for example, is a local variable in the scope of the __init__ function, but self.job is an attribute of the instance that’s the implied subject of the method call. They are two different variables, which happen to have the same name. By assigning the job local to the self.job attribute with self.job=job, we save the passed-in job on the instance for later use. As usual in Python, where a name is assigned (or what object it is assigned to) determines what it means.
Speaking of arguments, there’s really nothing magical about __init__, apart from the fact that it’s called automatically when an instance is made and has a special first argument. Despite its weird name, it’s a normal function and supports all the features of functions we’ve already covered. We can, for example, provide defaults for some of its arguments, so they need not be provided in cases where their values aren’t available or useful.
To demonstrate, let’s make the job argument optional—it will default to None, meaning the person being created is not (currently) employed. If job defaults to None, we’ll probably want to default pay to 0, too, for consistency (unless some of the people you know manage to get paid without having jobs!). In fact, we have to specify a default for pay because according to Python’s syntax rules, any arguments in a function’s header after the first default must all have defaults, too:
# Add defaults for constructor arguments
class Person:
def __init__(self, name, job=None, pay=0): # Normal function args
self.name = name
self.job = job
self.pay = pay
What this code means is that we’ll need to pass in a name when making Persons, but job and pay are now optional; they’ll default to None and 0 if omitted. The self argument, as usual, is filled in by Python automatically to refer to the instance object—assigning values to attributes of self attaches them to the new instance.
Testing As You Go
This class doesn’t do much yet—it essentially just fills out the fields of a new record—but it’s a real working class. At this point we could add more code to it for more features, but we won’t do that yet. As you’ve probably begun to appreciate already, programming in Python is really a matter of incremental prototyping—you write some code, test it, write more code, test again, and so on. Because Python provides both an interactive session and nearly immediate turnaround after code changes, it’s more natural to test as you go than to write a huge amount of code to test all at once.
Before adding more features, then, let’s test what we’ve got so far by making a few instances of our class and displaying their attributes as created by the constructor. We could do this interactively, but as you’ve also probably surmised by now, interactive testing has its limits—it gets tedious to have to reimport modules and retype test cases each time you start a new testing session. More commonly, Python programmers use the interactive prompt for simple one-off tests but do more substantial testing by writing code at the bottom of the file that contains the objects to be tested, like this:
# Add incremental self-test code
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
bob = Person('Bob Smith') # Test the class
sue = Person('Sue Jones', job='dev', pay=100000) # Runs __init__ automatically
print(bob.name, bob.pay) # Fetch attached attributes
print(sue.name, sue.pay) # sue's and bob's attrs differ
Notice here that the bob object accepts the defaults for job and pay, but sue provides values explicitly. Also note how we use keyword arguments when making sue; we could pass by position instead, but the keywords may help remind us later what the data is (and they allow us to pass the arguments