Teaching Python to Engineers

Engineers need a different approach to learning Python than what I see in existing materials. Learning Python, 2nd ed. is the best introductory text, suitable for a one-semester course, but it is too long for a busy engineer. The key concepts are too diffuse. The core presentation could be much shorter, with exercises and examples keyed to specific parts of the text.

The more concise presentations tend to assume the reader has lots of experience with Java or some other language. Engineers are smart, but we should not assume any prior experience with computer languages.

Object-Oriented Programming

The key to effortless introduction of classes and objects is to build on what students already know from earlier topics ( functions and modules ). A class is not much more than a package of data and functions similar to a module. Of course, it is much more powerful in its applications, but when introducing classes, it is best to avoid mystification, and focus on just the few differences between modules and classes.Once students have a good understanding of what's new with classes, they can move easily into theirmore powerful applications.

Emphasize Parallels between Classes and Modules

Drawing on these parallels will make learning about classes a little easier, especially on some tricky points involving the special behavior of inherited variables.

  1. Functions have the same form as methods, except for instance variables (a fundamental difference) and the special first argument (a Python trick). Focus on the differences.
  2. Re-assigning an instance variable creates a new object in memory. Any reference to an inherited object is over-ridden. Compare to module reloads.
  3. Bound functions preserve instance variables, only if those variables are in the namespace dictionary of the instance. Inherited values can change. Compare with direct references to objects in a module.

Use Realistic Examples

Concrete examples should be the center of almost every discussion, and these should be presented at the beginning of each topic. This is especially true for engineers, who can quickly grasp the workings of a machine or circuit, but will fall asleep in an abstract discussion of namespaces. Before you use the term "namespace", show a concrete example of the __dict__ of an object. The id() function will make an object "real" by showing its address in memory. sys.getrefcount() will help solidify any discussion of Python's variables and their connection to real objects.

I've chosen the animal kingdom as my universal example, instead of "spam" and "foo", which lose their cuteness very rapidly. Anyone who has taken 8th grade biology can relate to this hierarchy of objects. Thehierarchy can be expanded as new topics are introduced, and it gives the feeling of a real program, while not overwhelming the student with complexity. The complexity is there, but it is added in small increments, so it does not have to be re-learned with each example. Animals_2.py looks big and complex, but it is almost all familiar pieces after studying Animals_1.py.

Shun Mystery

Even Python has some unnecessary complexity in its syntax. Computer languages are like a rug just a little too large for a room. Smooth the wrinkles in one place, and they pop up somewhere else. A well-designed language like Python has the wrinkles out at the edges where you don't trip on them very often.

The best way to deal with wrinkles is to simply acknowledge them, learn the trick, and don't try to justify them with some deeper meaning. Static methods ( a wrinkle which resulted from assuming all methods have 'self' as the first argument ) are not some advanced form whose true purpose can only be understood by experts. These are introduced in the Animals_2 example, and explained in a footnote. That is all they deserve.

Minimize CISJargon

Complex terminology like "static method" may seem familiar to CIS students, but it only confuses engineers and conveys no real meaning. Why is it "static"? What would a "dynamic" method look like? In cases like this, it is better to just make up a word, than to borrow one from some other language with all its implied baggage. Static methods in Python could be called "noself" methods, meaning very simply – there is no self object as a special first argument. Then nobody would be confused trying to understand the "static" nature of a noself method.

Lambda functions have the same problem. This term is supposed to convey great meaning for those who understand lambda calculus. Listening to experts justify the use of this term is like listening to lawyers explain why they can't use plain English. We will use "nameless function", and de-emphasize the importance of lambda calculus to an understanding of Python.

Unless there is some fundamental distinction to be made, and properly explained, it is better to have fewer and simpler terms. Class methods, static methods, instance methods, and lambda functions are all just functions. We will compromise and use the term "method" to mean any of the methods above, but only because "method" is such a widely used term that an engineer working with Python will be hearing it anyway. We will emphasize, however, that methods are nothing but functions inside a class definition.

Some other terms that seem strange to engineers:

class – Remember, we are assuming no experience with Java!! What does "class" mean to a non-CIS student? It's a grouping of similar items, awfully vague in this context! "Prototype" or "template" would be much closer in meaning to what a class actually is. Again, we will use "class", because the term is already so widely used.

Long Motivational Discussions are a Distraction

Engineers already understand structured design, modularity, encapsulation, and clean interfaces. They just need some pointers on how the new language can help them achieve these goals.

Advanced Topics

Types and Objects

object is the built-in object that serves as the ancestor of all new-style classes. type is an object of type 'type' that serves as the ancestor of all built-in types and metaclasses. If you find triple entendre in a programming language more annoying that amusing, just remember this diagram:

object --> type --> metaClassX

| | --> inheritance right -->

\ - --> - --> ClassX |

| instantiation down

objX

Exercise

Write the simplest possible program with a metaclass, a class, and an instance of that class. Use the functions type(), issubclass(), isinstance(), to explore the relationships among these objects.

> object

<type 'object'>

> type

<type 'type'>

> issubclass(type,object)

True

> class metaClassX(type): pass

> class ClassX(object):

__metaclass__ = metaClassX

> objX = ClassX()

> isinstance(objX,ClassX)

True

> isinstance(objX,metaClassX)

False

> isinstance(ClassX,metaClassX)

True

> isinstance(ClassX,object)

True

> issubclass(ClassX,object)

True

> issubclass(metaClassX,type)

True

> issubclass(metaClassX,object)

True

> issubclass(ClassX,type)

False

> type(ClassX)

<class '__main__.metaClassX'>

> type(metaClassX)

<type 'type'>

> dir(object)

['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__']

> dir(type)

['__base__', '__bases__', '__basicsize__', '__call__', '__class__', '__cmp__', '__delattr__', '__dict__', '__dictoffset__', '__doc__', '__flags__', '__getattribute__', '__hash__', '__init__', '__itemsize__', '__module__', '__mro__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__subclasses__', '__weakrefoffset__', 'mro']

> ClassX

<class '__main__.ClassX'>

> dir(ClassX)

['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__']

> dir(objX)

['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__']

> ClassX.__dict__

<dictproxy object at 0x008FD590>

> ClassX.__dict__.keys()

['__dict__', '__weakref__', '__module__', '__metaclass__', '__doc__']

> object.__dict__.keys()

['__setattr__', '__reduce_ex__', '__new__', '__reduce__', '__str__', '__getattribute__', '__class__', '__delattr__', '__repr__', '__hash__', '__doc__', '__init__']

### metaMadness.py

class metametaClassX(type):

mm1 = 'mm1'

def __init__(cls,name,bases,cdict):

cls.mm1 = metametaClassX.mm1

class metaClassX(type):

__metaclass__ = metametaClassX

m1 = 'm1'

def __init__(cls,name,bases,cdict):

cls.m1 = metaClassX.m1

cls.mm1 = metaClassX.mm1

class ClassX(object):

__metaclass__ = metaClassX

c1 = 'c1'

objX = ClassX()

for x in objX,ClassX,metaClassX,metametaClassX: print type(x)

###

TeachingNotes.docPage 1 of 5DMQ 6/30/04