Explantion of implementation issues and Python syntax in mod_arith1.py

I said a lot of this in class, but I did not write it down before. Hopefully this is a better reference.

Classes are "first-class" objects in programming language jargon, so they can be returned by functions and assigned to variables.

I am not going to do anything fancy wih super classes.  Leave that to advance OOP courses.  In this case I have AnyMod just to provide a name. It is a trivial class, with only a name and no internal logic.  All I care is that for any version of the class Mod, an instance of a modular number, x, generated from a subclass of AnyMod satisfies  isinstance(x, AnyMod) .

The indentaton in ZMod for class Mod is just the indentation that comes from being defined inside the function ZMod.  Usually subclasses are not define inside other things.  In that case it would have the same indentation as any other class, like AnyMod (class at the left margin).  What makes Mod a subclass is the heading, referring to AnyMod as a super class:

class Mod(AnyMod):
 
By the way, Python allows multiple inheritance, and there can be a list here, but that is getting way more advanced than I mean to.  This is likely to be the only reference to superclasses in the course. 

An issue is that the general code in the function ZMod returns an unnamed class.  When code calls ZMod with a literal modulus value, it makes sense to assign a logical name like
Mod26 = ZMod(26)

ZMod is the creator of classes, returning a class object.  You are used to creating an instance of a class using the name of an explicit class, like
snake = Animal("Hissy")
so this looks similar to Java (except without the "new").  In Python, with classes themselves as objects, what actually happens under the hood is a bit different, Animal is just a reference to a class object, and the class object itself serves as the constructor function to make an object of that class.

To continue with our example, to create v = 3 mod 26
v = Mod26(3)

If I want to create
v = x mod m
on the fly, and not remember a name for the class in the middle, I can combine the steps above:

ZMod(m) returns a class of mod m arithmetic, which serves as the constructor for numbers mod m, so to get the specific number x mod m:  
v = ZMod(m)(x)

__str__ and __repr__

As I say in the JavaVsPython.html, both __repr__ and __str__ are similar to the toString method in Java.  In both languages they provide default conversions to a string representation for a class of objects (used implicitly, for instance by print methods).   Java's toString is closer to __str__, since toString in Java has no specified form for the string returned, similar to __str__.  The intention of __repr__ is much more specific:  an expression that the interpreter could evaluate and turn into an equal object.

Methods toString and __str__ just format the description of an instance variable state.  The format you choose for them is open.  For instance in the modular arithmetic classes I produce strings like "3 mod 5".  A human should understand that, but Python could not automatically parse that string back into a Mod object. On the other hand Python can parse the string from __repr__ back into a Mod object.  "ZMod(5)(3)" is not an appealing format for humans, but it does the trick for the Python interpreter, so a string in that form is returned by __repr__.

You are not likel to use __str__ or __repr__ directly.  They provide the implementation details.  The common functions to use are str and repr, where str(obj) generates a call to obj.__str__() and repr(obj) generates a call to obj.__repr__().  Again str and repr are used implicitly in different forms of printing.

Type mixing in operations

For methods like __add__, the idea is to define the standard infix operators:  If x and y are defined as modular numbers, x+y generates a call to x.__add__(y).  In  this actual method call the actual parameters x and y map to the formal parameters in __add__, self and other.   Similarly, C++ has something like operator '+' as a method name to define the infix '+' operation for a particular class of object.  (I'm probably not exactly right in remembering the syntax.)

The fancier Mod package, mod_arith1.py, allows Mod numbers to be mixed in operations with other things - at this point just the two Python 2.X forms of integers.

I could have written the __add__ method:

    def __add__(self, other): # used by + infix operand
        'Return self + other, if defined'
        if isinstance(other, (int, long)):
             return Mod(self.value + other)
        if isinstance(other, AnyMod) and other.modulus() == m:
             # other is the same kind of Mod object
             return  Mod(self.value + other.value)
        return NotImplemented

A bunch of other operations of Mod objects could use similar code, testing if the other operand can be converted to the same kind of modular object, and then actually getting an integer representative of the converted object.

This code could be repeated over and over - a red flag in programming.  In particular we might come up later with other classes that can be compatibly be operated on with Mod objects, and then my code would need to be changed in multiple places.

Instead, I followed good programming practice and came up with a way to isolate these operations:
The __int__ method is something you would not call directly.  If you use int(x), it is evaluated using x._int__(), if defined.  In my Mod classes the__int__ method just returns self.value.

With these methods set up, the arithmetic operation methods became shorter, and did not need to explicitly code what can be converted or how.   In particular, the __add__ method I do use:

    def __add__(self, other): # used by + infix operand
        'Return self + other, if defined'
        if self._isCompatibleType(other):
            return Mod(self.value + int(other))
        return NotImplemented