1. Overview: Python classes are a cross between C++ classes and Modula 3 classes. Classes are themselves objects and support multiple inheritance.

  2. Class declaration
    1. Syntax
      class classname:
         variable initializations
         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
      
    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 = []
        

  3. 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

        1. Old-style classes: 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)

          1. old-style classes are created by default and are the ones we will use in this course, since we are using the Python 2.7 interpreter
          2. old-style classes were abolished in Python 3, but classes defined in the old way will still appear to behave like old-style classes

        2. New-style classes: new-style classes have at least one diamond relationship because all new-style 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).

          1. New-style classes were introduced in Python 2.2
          2. New-style classes are the only class available after Python 3, but they are still not part of the standard Python documentation, so their integration into the language is currently rather poor.