Anatomy of a CLASS

Anatomy of a Class – an overview for beginners

While we can easily strain the analogy, in the parlance of language, every command in Python is a verb while every piece of data or data type is a noun. Let’s run with this little metaphor for a minute.

Verbs=functions

If you want your very own custom verb you create a function with the “def” structure. Functions are easy and straightforward. “Do this, multiply that, return this”, etc. They are highly structured and dependable in Python.

Functions manipulate variables. Possibly as a beginner your biggest hang-up with functions will be learning to deal with the scope of variables. You should have a good understanding of namespaces and scope before going further.

Namespaces and Scope

A computer has a whole bunch of memory, but usually parts of it are locked into protected areas so that only a specific program or even a specific part of a program can go inside those areas. Think of these areas, called namespaces (sometimes called name scopes) as being like the characters in the holodeck on the Starship Enterprise. The programming and memory of a namespace, like the holographic characters, only have existence inside that special room.

Usually a named variable only has meaning and value inside its namespace – that is the “scope” of its existence. It does not even exist unless you are in the holodeck with it. This is called a “local” variable. Occasionally we create a special variable (while we are in the holodeck, i.e., inside the namespace) that we want to mean something to the whole “ship” – we call that a “global” variable. We assign that special status to it using the global command. (Like in that episode when Scotty recreated the bridge on the holodeck for some reason.) The important part is that a class has its own namespace, so its variables are usually local.

Put this little demo in IDLE and run it to see the truth of what we said:

#test local def variables
def doSomething(a,b):
    global d
    d=a+b
    a*=2
    b*=2
    c=a*b
    return(c)
a=1
b=2
c=3
x=doSomething(a, b)
print(a,b,c,x,sep=', ')
print(d, d*c,sep=’, ‘)

The output demonstrates variables with identical names do not interact if they are in different namespaces unless they are defined as global in a function – the output will be:
1, 2, 3, 8
3, 9

Nouns=class

So, if we “make verbs” with functions using “def”, how do we make nouns? Keep in mind that Python is an “object oriented” programming language (some languages are not, like Fortran, Basic and Pascal). Everything in Python is an object and belongs to a “class” of objects.

Your very own Python noun is a brand-new thing or object and must have a class to which it can belong. As a member of its class, it is “preconfigured” to have one or many attributes and may be able to perform one or many actions, i.e., have its own functions. So, your first step is to create a blueprint for this complex data object with the “class” command structure.

class myClassname:
pass # just to demo basic structure

Once having created this blueprint – this new class – you can tell Python to create one or many “instances” of your object. Each one you build from your blueprint is called an instance of the class, and has its own individual name and possibly unique values for the attributes that you define.

For example, suppose we are programming pirates for the holodeck. Every pirate is a different object, but all pirates have a LOT in common. If we only needed one pirate we could just set up variables for his headwear, weapon, shirt type, rank, etc. But what if we need 20 pirates? Would it not be faster to define all the characteristics they have in common – and all the actions they can take – in just one fell swoop? Then, all we would have to do is tell the computer to make one of those each time we create a new pirate, and we would individualize that pirate, that instance, with the special different characteristic values we want it to have.

In other words, what we should do is set up a CLASS of object called “pirate” with standard characteristics (attributes) and functions. Attributes are the names we assign to the variables that hold values for our standard characteristics. For example, each pirate will have some rank, headware, weapon, and type of shirt, but each instance of our pirate class may have different values for each of its attributes.

Structure and Components of a Class Statement

It seems obvious, but note that you must define a class before you use it.

 

When you create a class, it establishes its own namespace and all its own local variables (except global definitions) exist only inside that namespace. They do not interact with other variables of the same name outside it. This leads us to one very important “feature” of classes that you need to know. If you use the same word to designate some specific value both inside and outside the class blueprint, the instance value will take precedence when you try to use that value.

Class definitions, like function definitions (def statements) must be executed before they have any effect. Class definition are clearest when overtly defined, but they could be placed in an “if” statement, or even inside a function.

Header Line

As you can see in the graphic, if you have no inheritance to worry about, (it is optional) you begin a class definition with the word “class”, followed by your name for the class, followed by a colon. If the class you are creating is a “subclass object” (if it is a child claiming a parent), then after your name and before the colon you will have a set of parentheses containing the name of your “superclass” (parent) object to which it belongs and from which it inherits.

Inheritance is a feature of many “object oriented” computer languages. It means a “child” object can be created that starts out with all the features, attributes, and functions of an existing “parent”. This allows us to avoid reinventing the wheel entirely in the right situations. Applied to classes, the parent is called the superclass and the child is called the subclass.

This comes into play a lot when you are using tkinter to implement a graphic user interface (GUI). For example, (and assuming you are not also using the ttk module) you may want a dozen “Button” objects, all with seven or eight of its options set identically to your specification. To do that you might create:
class mybutton (Button):
which would inherit all the important stuff that makes a button work, then you set the appearance options to suit yourself in the rest of your class blueprint. Creating instances of this class can yield a whole bunch of identical buttons that look, and respond, the way you want them without having to code the same instructions repeatedly.

Docstring – line 2

If you are going to have a docstring, say a multiline description, it starts on the next line with a set of triple quotes and then another set of triple quotes must bracket the last line. A single line still must be placed between triple quotes. See the graphic for more multi-line recommended format. A docstring is an attribute of your class. It can be retrieved with the .__doc__ command.

Attribute References

A class is usually a collection of attribute references (either data or method attributes) defined for either the class as a whole or for an instance of the class.

Class Data and Method Attribute References

There may be a circumstance which makes it convenient to store a value as a part of your class definition. Perhaps you need to use it repeatedly across all instances. You have a few options. You could define a global value (like “global myconstant”) which would allow you to change the value outside your class definition. You could set it as part of your instance initialization (“def __init__(self, etc…)”) by adding a line like “self.mycon=3.1414” or you could set it up in your class definition as a class data attribute reference. It could look like this demo that shows how variables do (or don’t) interact:

class test:   
    mycon=1.5 
    def __init__(self, testnum):
        self.x=float(testnum)
    def mult(self, num):
        self.x=float(num)
        return(self.x*self.mycon)
    
ex1=test(0.0)
y=input("a number: ")
print(ex1.mult(y))
print (ex1.mycon)   #both hold 1.5
print (test.mycon)
print()
test.mycon=3
print (ex1.mycon)   #now both hold 3
print (test.mycon)
print()
ex1.mycon = 9.2
print (ex1.mycon)   # holds 9.2
print (test.mycon)  # still holds 3
ex2=test(1.1)        # create a new instance with a unique value
print()                   # a newly initialized instance
print(ex2.x)           # holds whatever the current 
print(ex2.mycon)   # class value of mycon is (3)

It is also possible you might want your class to hold a function that does something which has no interaction with any instance data – a class method attribute reference. The following example serves no purpose, but it works even without an instance being created:

class cmar: # class method attribute reference
def HW():
return(“Hello World”)
print (cmar.HW())

Note we called the function of the class, not an instance. To call it with an instance would need to include *args in the function definition like this:

class cmar:
def HW(*args):
return(“Hello World!”)
testcmar=cmar()
print (cmar.HW()) # print the class variable value
print (testcmar.HW())

Class functions can return the results of instance variable manipulations of just about any complexity imaginable. Data is given back to the calling variable with the “return” statement just like any other function.

 

Instance Data and Method Attribute References

You create an instance by assigning a new local variable in such a way that it calls the class definition to produce a new instance. We define names for instance variables using the special built-in __init__(self) statement in your class definition (your blueprint). The first thing you must list in the parens is “self” so that Python will know how to fill in the lookup information it uses to recall your values in the future. Hold that thought, more about self in a minute.

Putting the __init__() statement right below your doc string is considered safe and practical. You will assign initial values to the variables you name and create in your __init__() statement when you use a variable assignment to call your class and instantiate your new instance. Here is what typical code for this occurrence may look like, along with another demonstration of how class variables can be overridden. Run this twice, the second time delete the # beginning line 20.

class Pirate:
    '''This is a Pirate class, matey!'''
    def __init__(self, nm, wpn):
        self.weapon=wpn  #"self" holds the calling instance name
        self.name=nm      # while the class function is executed
    def say_name(self):     # so Python knows what data to look up 
        return("Arggg...My name is " + self.rank + " " + self.name)
    patch="Skull"  #class method attribute reference
name="Pete"    
P1=Pirate(name,"sword")   #with instantiation 
P1.rank= "First Mate"       #after instantiation
print(P1.say_name(),"\n")
'''
Try this with and without the P1.patch line to
demonstrate that if you use the same variable name an
instance variable will override a class assignment.
'''
print("Hello..My name is " + P1.name)
print("I bear this patch: ", P1.patch)
print("... other pirates have the",Pirate.patch)
#P1.patch="Jolly Roger"
Pirate.patch="Trident"
print("Now most pirates bear this patch:", Pirate.patch)
print("...and Pete has a :", P1.patch,"\n")

Somewhere in your code you decide to create a pirate named “Pete” – you will have lots of different pirates, so you have created a class for them with: “class Pirate:”.
You know pirate P1 (his name is in variable “name”) carries a sword. So, you create this class instance with his name and weapon.

But before he can introduce himself he has to have a rank. You discover he is a First Mate but P1 (Pete) has already been initialized. No problem. You just set up your instance’s rank with P1.rank= “First Mate …and Python adds that to a dictionary database holding information about pirate P1.

The values to be initially assigned are placed inside the parentheses of your class call, separated by a comma, in the same order that you set up variable names to receive them in your definition. When we go to set them up in our class (with the def __init__() function), note that “self” must be the first thing in the parens – more on that in a minute.

The next lines assign those values to instance specific variables which will, from now on, ONLY be associated with this instance – again, the use of “self” is essential to make this happen. The term “self” is a universally accepted convention and is a “proxy” for the name of the instance that calls it for variable initialization or recall or for reference in methods.

Our class also includes an instance method attribute reference – a function – which allows any instance to introduce himself with his name and rank:
def say_name(self): #self so Python knows what data to look up

return(“Arggg…My name is ” + self.rank + ” ” + self.name)

 

Consider Our Self for a Minute

The need for, and use of, the “self” term in our example (and any other class example you will ever find anywhere) causes more trouble for new Pythonistas than just about anything else. You will find numerous tutorials that declare “self” a Python key word. It is not. Take it from the horse’s mouth – the Python Software Foundation docs: “Often, the first argument of a method is called self. This is nothing more than a convention: the name self has absolutely no special meaning to Python. Note, however, that by not following the convention your code may be less readable to other Python programmers, and it is also conceivable that a class browser program might be written that relies upon such a convention.”

The essential point is that some variable must be used to refer to the newly created instance of a class object during the instantiation process as well as to identify an instance in future attribute and function calls and “self” has been universally adopted to fill this temporary need. When you call an instance function, an instance id is going to be sent “invisibly” as the first piece of data whether you like it or not. Just go with the flow and note from examples where you must use it. One day something with click and it will suddenly all make sense.

Getting Information on Your New Class

If you find you need a list of the functions and attributes in your class, the .__dict__ method will provide you a list, though you may have to tease out what you want. If we add this code…

x=(Pirate.__dict__)
for pirateStuff in x:
spaces =” “*(13-len(pirateStuff))
print(pirateStuff + spaces + “: ” + str(x[pirateStuff]))

…to the example above, you will get the stuff that is easy:

__module__ : __main__
__doc__ : This is a Pirate class, matey
__init__ : <function Pirate.__init__ at 0x05053618>
say_name : <function Pirate.say_name at 0x0543BB70>
patch : Trident
__dict__ : <attribute ‘__dict__’ of ‘Pirate’ objects>
__weakref__ : <attribute ‘__weakref__’ of ‘Pirate’ objects>

Another form includes the init attributes and will return the variables assigned: print(Pirate(‘nm’,’wpn’).__dict__) will yield the above plus:
{‘weapon’: ‘wpn’, ‘name’: ‘nm’}

If you need more than this, you may have to resort to importing the inspect module – which goes beyond an essay for beginners.

Using Class with tkinter

The use of class structures is very helpful when we have multiple widgets to create in tkinter, so, before we tie up this into to class, here is a tkinter example that does nothing but create seven variously colored labels and then lets the user change them, one-at-a-time, to blue. There is a LOT of stuff that is demonstrated in this little example, but it is fairly well documented.

Note that the upper part, the “standard set up header code”, is just a plain vanilla set up that we use over and over to test various tkinter ideas. You can copy and paste the whole thing, or you can just copy the top part (plus add a mainloop at the bottom) to try out our GUI header by itself.

#CLASS demo for multiple widgets in tkinter
#standard set up header code  
from tkinter import *
root = Tk()
root.attributes('-fullscreen', True)
root.configure(background='white')
scrW = root.winfo_screenwidth()
scrH = root.winfo_screenheight()  
workwindow = str(scrW-200) + "x" + str(scrH-150)+"+90"+"+70"
top1 = Toplevel(root, bg="light blue")
top1.geometry(workwindow)
top1.title("Top 1 - Workwindow")
top1.attributes("-topmost", 1)  # make sure top1 is on top to start
root.update()                   # but don't leave it locked in place
top1.attributes("-topmost", 0)  # in case you use lower or lift
#exit button - note: uses grid
b3=Button(top1, text="end it all", command=root.destroy)
b3.grid(row=0,column=0,ipadx=10, ipady=10, pady=5, padx=5, sticky = W+N)
#____________________________
#create a class that makes some labels
 
class myLabel(Label):
    def __init__(self, bkgnd, r, c):
        global selfhold # set a global to temporarilly hold the label name
        self.bkgnd,lrow,lcolumn=bkgnd,r,c #get the variable then create and grid the label
        self=Label(top1, background=self.bkgnd, width=40, text=self.bkgnd + " - label: " + str(r))
        self.grid(row=lrow, column=0, columnspan=2, padx=10, pady=10, ipadx=10, ipady=10)
        selfhold=self # grab the label name to add to our list
 
def callback(*args):
    if int(myinput.get())<1 or int(myinput.get())>7:  # just a little error check
        pass
    else:
        labellist[int(myinput.get())-1].configure(bg="blue",fg="white", text="Changed to blue!")
   
#some colors
mycolors=['pink1','coral1','gold2','skyblue','yellow','grey75','green yellow']
labellist=[]
# now call the class to make our seven labels with unique colors
for i in range(7):
    aLabel=myLabel(mycolors[i], i+1, 0)
    labellist.append(selfhold) # and stuff each label name in a list
# do something when user hits return
top1.bind_all('<Return>', callback)
# tell user what is expected and what will happen
instruction=Label(top1, relief=SUNKEN, bg="linen", text="Enter 1 to 7 to turn it blue")
instruction.grid(row=8, column=1, padx=10, pady=10, ipadx=10, ipady=10, sticky=W)
# create the user entry widget
myinput=Entry(top1, relief=SUNKEN, takefocus=1, bg="white")
myinput.grid(row=9, column=1,padx=10, pady=10, ipadx=10, ipady=10, sticky=W)
myinput.insert(0,0)
myinput.focus_set()
#____________________________
root.mainloop()