1. Overview: Python, and most scripting languages, use a prototype-instance model model rather than the class-instance model model used by most compiled languages. Python calls the templates it uses to stamp out objects classes but they are really prototype objects. The major differences between a class-instance and prototype-instance system are as follows:

    1. classes and prototypes both provide a blueprint for creating objects but prototypes are themselves objects that can be manipulated at run-time. By contrast, classes are immutable--their instance variables and methods are defined at compile-time and neither they, nor their instance objects, may be structurally modified at run-time.
    2. both prototype and their instance objects may have their structure modified at run-time. That means that they may have new instance variables added, may have instance variables deleted, may have methods added or deleted, and may modify a method's implementation. When a prototype object is structurally modified, the modifications are made to all instance objects as well. The following notes describe this process in more detail.
    Python's prototype-instance model supports multiple inheritance and it also supports operator overloading. We will not discuss operator overloading in this course.

  2. Class declaration
    1. Syntax
      class classname:
         variable initializations  # these tend to be class variables
         fct definitions
      
    2. Example
      class Point:
         x = 0
         y = 0
         def addPoint(self, p1):
            newpoint = Point()
            newpoint.x = self.x + p1.x
            newpoint.y = self.y + p1.y
            return newpoint
      
      self is like the this pointer in Java and C++ but must be explicitly declared. It is always passed as the first argument to a method. If you want to access instance variables, then you must prefix the variable's name with self as is done in the above example. If you fail to do so, then instead of getting access to the instance variable, you will be either reading from a local method variable or, if using an assignment statement, creating a new local method variable with that name.

    3. Classes are objects so you may dynamically add and delete both attributes and functions

      1. You can declare a function outside a class and then assign it to the class. It will become a method for all instances, including existing instances. This is a good way to fix a function without having to shutdown the Python interpreter.
      2. Each class and each instance has a dictionary variable called __dict__ that stores all instance variables and function definitions
      3. del Point.x: deletes x from Point

    4. Instance variables (called attributes or properties in Python)

      1. Values of instance variables are inherited by instances, but may be overridden: Python first looks in the instance's __dict__ dictionary and if it does not find the attribute, then it looks in the class's __dict__ dictionary
      2. Referenced using the dot (.) operator
      3. All attributes are public. A convention for implying that an attribute is private is to put a _ before it. For example:
        class Stack:
          _top = 0
        
      4. Variables with primitive types are instance variables, but variables with mutable types, such as lists or dictionaries, act like class variables if you modify the value, as opposed to replacing the value. For example:
        >>> class Dog:
        ...    tricks = []
        ...    def addTrick(self, trick):
        ...       self.tricks.append(trick)
        ... 
        >>> smiley = Dog()
        >>> ebber = Dog()
        >>> smiley.addTrick("beg")
        >>> ebber.addTrick("fetch")
        >>> print (smiley.tricks)
        ['beg', 'fetch']
        >>> print (ebber.tricks)
        ['beg', 'fetch']
        
        If we replace the value, then the attribute becomes a local attribute
        >>> smiley.tricks = ['beg']
        >>> smiley.tricks
        ['beg']
        >>> ebber.tricks
        ['beg', 'fetch']
        
        See constructors for a technique that allows you to create local attributes with mutable values.

    5. Methods

      1. Pointer to an instance is automatically passed to a method but this pointer must be explicitly declared as the first parameter to the method. By convention, Python programmers declare this variable as self

      2. When a function is defined in a class declaration, it becomes a function in the resulting class object, but a method in any instances.
        1. Subtle but important difference between a function and a method.

          1. The programmer must explicitly pass an instance object to a function
          2. A pointer to an instance is implicitly passed as the first argument to a method.
          3. The following two calls are essentially identical, and Python essentially translates the second call into the first call:
            a = Point()
            b = Point()
            c = Point.addPoint(a, b)     // function call
            c = a.addPoint(b)            // method call
            

    6. Constructors: The __init__ function (two underscores before and after init) is the constructor for a class

      1. Only one __init__ function is allowed
      2. Attributes that store mutable values should be initialized in the constructor:
        class Stack:
          def __init__(self):
            self.top = 0
            self.data = []
        
        It is also a good idea to define any instance variables in a __init__ function and to define class variables outside the __init__ function in the general body of the class statement.

  3. Class Instantiation

    1. To create an instance of a class, call the class name as though it is a function, passing any arguments that you wish to the constructor:
      myPoint = Point()     # if the constructor takes no arguments
      myPoint = Point(x, y) # if the constructor takes two arguments
      
    2. Sometimes a programmer just wants a C-style struct. The way to obtain that is to create an empty class body as follows:
      class Student:
        pass
      
      Then the programmer can create instances of Student and assign attributes on an ad-hoc basis:
      s = Student()
      s.gpa = 3.1
      s.name = 'Brad'
      
      Of course one could also create a constructor for such a class that initializes a number of variables to starting values.

    3. If you want a simple object without having to create a class, which is something you can do in other scripting languages like Javascript, use a dictionary instead of a class. You will not be able to attach methods to such an object but you will be able to access it using key-value pairs.

  4. Inheritance

    1. Syntax
      class DerivedClassName(BaseClassName):
          
          .
          .
          .
          
      
      For example:
      class LabradorRetriever(Dog):
          hair = 'short'
          personality = ['friendly', 'good with children', 'love to fetch']
      
    2. All methods are virtual, so a subclass can always override its base class's methods
      1. Can call base class method using BaseClassName.methodname(self, arguments): Note that because you are referencing the base class object, you must pass the self argument explicitly, since you are actually calling a function, not a method

    3. Multiple Inheritance

      1. Syntax
        class DerivedClassName(Base1, Base2, Base3):
            
            .
            .
            .
            
        
      2. Resolving attribute names and method names: Generally it appears to be depth-first, left-to-right through the base classes (i.e., first the inheritance hierarchy for Base1 is searched, then Base2, and so on until the name is resolved). More accurately, classes have at least one diamond relationship because all classes inherit from object. To keep the base classes from being accessed more than once, a dynamic algorithm linearizes the search order in a way that preserves the left-to-right ordering specified in each class, that calls each parent only once, and that is monotonic (meaning that a class can be subclassed without affecting the precedence order of its parents).