[Tutor] Help understanding classes
Steven D'Aprano
steve at pearwood.info
Sun Nov 16 07:46:40 CET 2014
On Sat, Nov 15, 2014 at 06:19:56PM +0000, Bo Morris wrote:
> Thank you Alan and Danny. It amazes me at the lengths you guys, as well as everyone else who contributes, will go to to help explain things to us; it is greatly appreciated!
>
> Alan, I decided to dumb down the learning classes just a little. By
> this I mean, I am not using Tkinter to learn classes. I am using one
> of the examples from your website, which I did change it just a
> little. I figured, I am having a hard time wrapping my head around
> classes and Tkinter would just add to the confusion.
Good plan!
Before I get to your example, let's dumb things down even more. What is
the *simplest possible* class we can create?
class MyClass(object):
pass
That's pretty simple. (Actually, we can make it even shorter by removing
the word "object", but that makes it more complicated because it will
work differently in Python 2 and 3, so let's not do that.) What do those
two lines of code do?
The header "class MyClass(object)" tells Python to define a new class
called MyClass. The body is the line "pass", which is just a placeholder
to tell Python that there's nothing else there. (Otherwise the compiler
will complain.)
MyClass inherits from "object". object is a special built-in name which
Python already knows about. The "object" class defines some extremely
basic behaviour, such as knowing how to print itself, and so our MyClass
inherits that behaviour.
Classes on their own usually aren't very interesting. There's not a lot
of work you can do with them, normally you work with *instances*. The
relationship between a class and its instances is similar to that
between a kind of thing and a specific example of that thing. E.g.:
class: Dog
instances: Lassie, Rin-Tin-Tin, Snowy, Hooch, Ol' Yella
class: President of the USA
instances: Barrack Obama, George Bush Jr, George Washington
class: Tool
subclasses: Screwdriver, Spanner, Hammer, etc.
instances: this red handled screwdriver, that 3" spanner
etc. Instances get their behaviour from their class; their class get
their behaviour either from code you program, or code they inherit from
the superclasses.
MyClass is a subclass of object, so object is a superclass of MyClass. I
can create an instance of MyClass, and then print it:
py> obj = MyClass()
py> print(obj)
<__main__.MyClass object at 0xb7b52f6c>
How did `obj` know what to print when I never wrote any code to handle
printing MyClass instances? It inherited that code from the superclass
`object`, which already knows how to convert itself into a
string, which print can then use:
py> str(obj)
'<__main__.MyClass object at 0xb7b52f6c>'
So far, every MyClass instance is indistinguishable except by their
"identity", their ID number which you can see written in hex in that
string display or by calling id():
py> id(obj)
3082104684
py> hex(id(obj))
'0xb7b52f6c'
[Aside: every single object in Python has an ID. It's usually going to
be a cryptic multi-digit number, but some implementations will instead
use an incrementing counter so that IDs will be 1, 2, 3, 4, ... ]
We can associate some data with individual instances. That makes
them distinguishable, and useful. What makes the ints 17 and 23
different is their *state*, that is the internal data (whatever that
is!) which distinguishes the instance with value 17 from the instance
with value 23. So far, our MyClass instances don't have any state, but
we can give them some.
Let's create a couple of instances of MyClass:
py> a = MyClass()
py> b = MyClass()
py> a.colour = 'red'
py> a.size = 23
py> b.colour = 'green'
py> b.size = 42
py> if a.size >= b.size:
... print("the %s instance is bigger" % a.colour)
... else:
... print("the %s instance is bigger" % b.colour)
...
the green instance is bigger
The colour and size attributes count as examples of per-instance state.
In a nutshell, that is what classes are all about: classes control the
state of the instances, and define their behaviour.
> So, I have the below code. When I run this from terminal, it obviously
> prints "This is a test." If I may, break the code down and ask
> questions as it pertains to the code?
>
> #################
> class Message:
> def __init__(self, aString):
> self.text = aString
>
> def printIt(self):
> print self.text
>
> m = Message("This is a test")
> m.printIt()
>
> ##################
>
> With the first part...
> class Message:
> def __init__(self, aString):
> self.text = aString
>
> Will I always use "_init_" when defining the first function in a
> class? I noticed on your website, you created a class where you did
> not use "_init_" (see below). Was this because you did not define a
> function?
Note that there are *two* underscores __init__ not one _init_. Such
"dunder" (Double leading and trailing UNDERscore) methods normally have
special meaning to Python, or are reserved for future use.
__init__ is one such special method. It is the "initialiser" method, and
Python will automatically call it when you create a new instance, so as
to initialise it. It is conventional to normally write it as the first
method in the class, but the position doesn't matter, only the name.
> class BalanceError(Exception):
> value = "Sorry you only have $%6.2f in your account"
This class doesn't contain an __init__ method because it just reuses the
one defined by Exception, the superclass or parent class. Think of
BalanceError being the child, when Python says to it "initialise a new
instance!" it says "I don't know how to initialise instances, I'll ask
my parent to do it -- Exception, initialise this instance for me!"
> I noticed that I can change "text" to anything and I still get the
> same results by running the code; I changed them to "blah" just as a
> test.
>
> When I define a function in a class, will I always use "self" as the
> first entry in the parenthesis?
*Nearly* always. Functions inside classes are called "methods", and
there are a couple of special purpose kinds of methods ("class methods"
and "static methods") that don't use self, but don't worry about them
for now. Ordinary methods will always use self.
Technically, the name "self" is arbitrary. You could use anything you
like, Python won't care. But it is a very strong convention to use the
name self, and unless you have a really strong reason to change you
should stick to that.
> On the next part...
> m = Message("This is a test")
> m.printIt()
>
> I noticed I cannot run "printIt()" unless I make it an object i.e.
> "m = Message("This is a test")...?"
Correct. printIt is a method that belongs to the Message class. It isn't
a global variable, so just trying to call printIt() on its own will
fail, there's no name printIt defined. (Or perhaps there is, but it is
something unexpected).
This is useful because it separates unrelated pieces of code. Your
Message class can define a printIt method to do what it needs it to do,
without having to worry about another class coming along and defining a
printIt method that does something completely different.
Both strings and lists have an index method, and they do similar things,
but work in different ways:
py> "abcd xyz bcd bc".index("bc")
1
py> ["abcd", "xyz", "bcd", "bc"].index("bc")
3
there's no conflict because the code for string index() lives inside the
str class and the code for list index() lives inside the list class.
> I noticed I could change "m = Message("This is a test")" to "m =
> Message(raw_input())," which works.
Correct. Message() doesn't care where the argument comes from, it only
sees the result, not the process of where it came from. All of these are
the same:
Message("hello")
Message("HELLO".lower())
s = "hello"; Message(s)
Message("h" + "e" + "l" + "l" + "o")
although of course some might be a bit more efficient than others.
> What if I wanted to create a function in Message that receives text
> from another function and then prints that text instead of the text
> from "m = Message("This is a test")...; can I pass or return values to
> another function inside a class?
Sure, no problem, methods can take arguments as well.
class Example:
def method(self, arg):
print("Instance %r was sent the message 'method %r'" % (self, arg))
py> x = Example()
py> x.method(42)
Instance <__main__.Example object at 0xb7b420cc> was sent the message 'method 42'
py> x.method('hello')
Instance <__main__.Example object at 0xb7b420cc> was sent the message 'method 'hello''
py> x.method([])
Instance <__main__.Example object at 0xb7b420cc> was sent the message 'method []'
But beware of writing methods that don't actually use self inside the
method body. If you never use self, that's a sign that it doesn't need
to be a method and should just be a top-level function outside of any
classes.
> The "self" is really throwing me off,
`self` is the instance that you are using. When I write:
x = Example()
x.method([])
Python effectively translates that into:
x = Example()
Example.method(x, [])
Look at the signature of method() -- the `self` argument gets x as its
value, and the `arg` argument gets the list [] as its value.
> when I think about creating different functions that do misc things
> just to practice. For example, I have a function that kills a Linux
> program. I just don't see how to rethink that function to where it
> could be defined in a class?
Ask yourself, what is the state that this class should manage? There
really isn't any, is there? That's a good sign that a class with methods
is not a great design for this.
> def kill_proc(process1):
> i = psutil.Popen(["ps", "cax"], stdout=PIPE)
> for proc in psutil.process_iter():
> if proc.name(process1):
> proc.kill()
>
> Would it be something like...?
> class processKiller:
>
> def _init_(self):
Since you don't have any data to attach to these ProcessKiller
instances, you don't need to initialise them, so no __init__ method is
needed.
>
> def kill_proc(self, process1):
> i = psutil.Popen(["ps", "cax"], stdout=PIPE)
> for proc in psutil.process_iter():
> if proc.name(process1):
> proc.kill()
>
> Then outside of the class, call it like so...?
> p = processKiller()
> p.proc.kill()
Correct, except that you forgot to pass the process ID to the kill_proc
method. Which you also misspelled :-)
Don't make the mistake of thinking that everything needs to be inside a
class. Classes are a great solution when you have data and functions
that need to be kept together.
Here's a cautionary tale from the Land Of Java of what happens when
language designers become too obsessed with putting everything inside
classes:
http://steve-yegge.blogspot.com.au/2006/03/execution-in-kingdom-of-nouns.html
Unlike Java, Python is happy to mix and match functions and object
methods as needed:
result = len(mystring) - mystring.count("a")
--
Steven
More information about the Tutor
mailing list