This is a brief note about the differences between functions and methods in Python, specifically with respect to the syntax of defining and calling them. It also reviews the various terms, ideas, and syntax related to classes and objects.
There is a glossary at the bottom of this page; please refer to it if you're confused by any of the phrasing used in the discussion below. I've tried to keep the discussion relatively concise, which means using shorthand terminology, so again, refer to the glossary if you need help decoding the shorthand.
In Python, the signature of a function (which is part of its definition) looks very similar to the code used to call that function.
def lineOverrun(text, line_length): return max(0, len(text) - line_length) line = "The quick brown log rolled down the lazy stairs." extra = lineOverrun(line, 80)
Note that the number of arguments specified in the signature is the same as the number of arguments we pass when we call the function. (To be really technical: the number of formal parameters is equal to the number of actual parameters.) When the function runs, the variable specified as the first argument in the function definition takes on the value of the variable given as the first argument in the call to the function. Same is true for the second argument. So the arguments exactly parallel each other in the call and the signature.
An object is an individual, distinct “thing” in the world of a program. Each object is an instance of a class — that is, it's a single entity with a distinct identity, but it is conceptually related to all other objects of that same class.
So one of the fundamental tasks of an object-oriented design process is to design and define classes, so that we can then solve problems with instances (objects) of those classes. In other words, before we can use a particular hammer to solve a problem, we must first define what a hammer is, conceptually.
A class describes a “conceptual bubble” — each instance of a class contains a number of data values, and is therefore in charge of them. The technical name for these data values is instance variables. (Member variables is an alternative name, particularly when we're talking about the formal members of a class, rather than things belonging to a particular instance of that class.)
Each instance of a class also carries along with it a selection of specialized capabilities — things that it can do, when asked by something on the outside, based on its knowledge and understanding of its member data. We express these capabilities by defining methods for the class. A method is just a specialized type of function that is part of a class.
Let's take a look at an example. Here we're defining the idea of a hammer that has a particular weight and a particular amount of wear. Every time we hit something with the hammer, the wear increases.
class Hammer: def __init__(self, initial_weight): self.weight = initial_weight self.wear = 0.0 def hit(self, nail_height, speed): force = self.weight*speed self.wear = self.wear + force return nail_height / force
Note that, after Python has executed this code, no actual hammers exist in the program! All we've done is define a class; we've said what a hammer would be like if we ever chose to create one and use it.
We've defined two methods for the class Hammer
: __init__()
and hit()
.
__init__()
is a special method called a constructor. A constructor tells the client (the code that uses a class) how to create a new object that's an instance of that particular class. Constructors always have the name __init__()
; it's a special name set aside by Python to designate constructors.
One of the primary jobs of a constructor is to define and initialize the member variables. Note that each member variable is specified by writing self.
and then the name of the variable. We've seen the .
operator before: it's how we access something that's a part of, or inside, something else.
So we can see that this syntax means that self.weight
is a variable, weight
, that is part of something else, namely self
. So what is self
? It's the special name given to the particular object that a given method is being called with. When a method is run on an object, within the instructions in that method, the object refers to itself with the name self
. Good name, huh?
So self.weight
means “the variable named weight
that belongs to the object currently under consideration”. In the constructor, that object is the object that's being created.
hit()
is just a regular method, a capability of objects of the class Hammer
. Note that it has two distinct effects: it both modifies the value of a member variable (and therefore modifies the state of the Hammer
object itself), and also returns a different value that it has computed. It can access its member variables by using the self.
syntax.
Methods are just functions, so the definition of a method is just a set of instructions that Python will follow when that method is called. A method is always called on a particular instance of the class for which the method is defined. So, in this case, we would only call hit()
with an instance of the Hammer
class. That particular object is what would be called self
when running the method.
Though methods are really just functions defined as part of a class, we call them using a slightly different syntax. Here I've expanded the code above to include some client code that uses the class to create an object and then work with it.
class Hammer: def __init__(self, initial_weight): self.weight = initial_weight self.wear = 0.0 def hit(self, nail_height, speed): force = self.weight*speed self.wear = self.wear + force return nail_height / force def getWear(self): return self.wear sledge = Hammer(5.0) nail = 2.0 print("Nail height:", nail) for hit_number in range(5): nail = sledge.hit(nail, 3.0) print("After a hit:", nail) print("Total wear:", sledge.getWear())
In this code, we first define the class Hammer
, along with all its properties (the member variables) and capabilities (the methods). Note that we've defined an additional method, getWear()
, in order to find out the wear on a given hammer once we've used it. (Stricly speaking, this method is unnecessary; we'll discuss this more in class. It's just here to provide an additional example of a method.)
After the class definition is some client code that uses this definition to create a Hammer
object and work with it. Let's go line by line:
First we construct a new instance of the class Hammer
, and call it sledge
. There are several interesting things to note about the process of constructing this object:
__init__()
, we call it using the name of the class. When Python sees client code use the name of a class as a function (that is, with open- and close-parentheses after it), it knows you're asking to construct an instance of that class, and so it calls the class's constructor.In the definition of the constructor, there are two arguments (two formal parameters). But we only had to pass one argument (one actual parameter — 5.0
) when we called the constructor.
This is because the first argument in the definition of a method is always self
, and Python always fills that in automatically. For a constructor, self
is the name given to the brand-new object that the constructor is responsible for creating.
The arguments in the signature after self
are assigned values respectively by the arguments explicitly passed into the constructor by the client. So, since initial_weight
is the first argument in the signature after self
, it gets the value 5.0
.
Within this loop, we call the hit()
method of our hammer. Note again that the syntax differs in a variety of ways from the usual way we call functions:
object.method(arguments)
. We call the object on which the method is called the “target” of that method call.When the method gets called, Python automatically makes the variable self
refer to the target of that call.
In the code above, the target of the call to the hit()
method is the object sledge
. So in the body of that method, when evaluating the call, self
refers to the same Hammer
object that sledge
refers to.
self
are assigned values respectively by the arguments explicitly passed into the method by the client. So nail_height = nail
and speed = 3.0
.getWear()
on sledge
to find out how much wear it has accumulated from us banging it against that nail. Once again, when Python evaluates the body of getWear()
, self = sledge
, and there are no further arguments to be assigned.There are a lot of equivalent terms or phrases used to express the same ideas when discussing topics related to objects and classes.
An individual thing that belongs to some class. We call this an “instance” of that class. Equivalent phrases to describe a particular object include:
Thingie
”Thingie
”Thingie
object”Thingie
”A variable stored as a part of an object. The class to which that object belongs is what defines the member variables that the object has. Member variables are also known as “data members” or “instance variables”, or collectively as “member data”.
No one on the outside should need to know or care about the internal details of the object's state; it is the responsibility solely of the object itself to keep track of its state and make sense of it. This is the essential purpose of object-oriented design: separation of concerns. Only the object needs to work with the member variables, and the logic of this work is encoded in the class's definition.
When we say that a particular object “has” a particular data member, we mean that that data member is defined as a part of that object, with a name that is only accessible using the dot operator on that object.
We may also say that a class “has” a particular data member, when all objects of that class have that data member. We can see this in the definition of the class because we will have written self.bar
to indicate the bar
data member that this class (and therefore every object of this class) has.
Synonyms for saying that an object or class has something are to say that it “owns” or “contains” that thing.
A method is a function that is defined as part of a class; conceptually, it is a capability of every object of that class. The primary purpose of a method is for client code to be able to ask a specific object to perform some action. We'll refer to the “target” of a method to indicate the particular object that was asked to perform that method.
The particular actions taken by the target when it performs some method may depend on the state of the target. An object, as a “conceptual bubble”, is in charge of understanding what its state means, and interpreting that state accordingly to decide what to do when performing the method.
This means that the client must specify a particular object in order to run a method: different objects have their own separate state, and so the computations involved in a method may be different for one object than for another. The client specifies the target of a method by putting it before the .
operator.
When the client specifies a target and asks the target to run some method, there are a variety of shorthand terminologies: we may say that it runs the method “on” the target, or “with” the target, or even “from” the target.
(There are a couple of exceptions to the “every-method-must-have-a-target” rule, but we won't really get into them.)
.
The .
operator is the way we access things that are defined as a part of something else. So foo.bar
means “the bar
that is part of foo
” or “the bar
that is inside foo
”.
The way you say .
when reading code out loud is “dot”.
So far in this course we have seen two different things come before the dot: modules and objects. (Remember, a module is what you get when you say import such_and_so
.)
We have also seen two different things come after the dot: variables and functions. When a function is defined as part of a class (and therefore is accessed by putting a dot after the name of an object of that class), we call it a “method”.