[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