#!/usr/bin/env python # coding: utf-8 #
# # # ## # # Object Oriented Programming (II) #
# # # # ## # # Classes have a bunch of special methods ### # the mirror of `__init__` is `__del__` # (it is the tear down during clean up) # In[ ]: class Bear: def __init__(self,name): self.name = name print(" made a bear called %s" % (name)) def __del__(self): print("Bang! %s is no longer." % self.name) # In[ ]: y = Bear("Yogi") ; c = Bear("Cal") # In[ ]: del y; del c # In[ ]: ## note that I'm assigning y twice here y = Bear("Yogi") ; y = Bear("Cal") # If we quit (not from the notebook but the interpreter or ipython) # # ```python # >>> f = Bear("Fuzzy") # ` made a bear called Fuzzy` # >>> exit() # `Bang! Fuzzy is no longer.` # BootCamp> # ``` #
# # __add__(self, other) # # __sub__(self, other) # # __mul__(self, other) # # __div__(self, other) # # __mod__(self, other) # # __divmod__(self, other) # # __pow__(self, other[, modulo]) # # __lshift__(self, other) # # __rshift__(self, other) # # __and__(self, other) # # __xor__(self, other) # ## In[ ]: get_ipython().run_cell_magic('file', 'bear1.py', '\nclass Bear:\n """\n class to show off addition (and multiplication)\n """\n bear_num = 0\n def __init__(self,name):\n self.name = name\n print(" made a bear called %s" % (name))\n Bear.bear_num += 1\n self.my_num = Bear.bear_num\n\n def __add__(self,other):\n ## spawn a little tike\n cub = Bear("progeny_of_%s_and_%s" % (self.name,other.name))\n cub.parents = (self,other)\n return cub\n\n def __mul__(self,other):\n ## multiply (as in "go forth and multiply") is really the same as adding\n return self.__add__(other)\n') # In[ ]: from bear1 import Bear as Bear1 # In[ ]: y = Bear1("Yogi") ; c = Bear1("Fuzzy") # In[ ]: our_kid = y + c # In[ ]: our_kid. # In[ ]: our_kid.parents # In[ ]: our_kid.parents[0].name # In[ ]: our_kid.parents[1].name # In[ ]: y.broken_limb = True # In[ ]: our_kid1 = y * c # ### Other Useful Specials ### #
# __dict__ : Dictionary containing the class's namespace. # __doc__ : Class documentation string, or None if undefined. # __name__: Class name. # __module__: Module name in which the class is defined. This attribute is "__main__" in interactive mode. # __bases__ : A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list. ## In[ ]: print(Bear1.__doc__) print(Bear1.__name__) print(Bear1.__module__) print(Bear1.__bases__) print(Bear1.__dict__) # ## "Hiding" class data attributes # In[ ]: class JustCounter: __secretCount = 0 x = 0 def count(self): self.__secretCount += 1 self.x += 1 print(self.__secretCount) # In[ ]: counter = JustCounter() # In[ ]: counter.count() ; counter.count() # In[ ]: print(counter.x) # In[ ]: print(counter.__secretCount) # no attribute is ever precisely private # # double underscore attributes are exposed as `object._className__attrName` # In[ ]: counter._JustCounter__secretCount # ### A note on referencing... ### # In[ ]: a = Bear("Yogi") # In[ ]: a # In[ ]: b = a # In[ ]: b # In[ ]: a.name = "Fuzzy" # In[ ]: b.name # In[ ]: Bear.bear_num # In[ ]: import copy # In[ ]: c = copy.copy(a) # In[ ]: c # new memory location # In[ ]: Bear.bear_num # In[ ]: c.name # In[ ]: c.name = "Smelly" # In[ ]: a.name # In[ ]: a.mylist = [1,2,3] # In[ ]: b.mylist # In[ ]: c.mylist # In[ ]: d = copy.copy(a) d.mylist # In[ ]: d.name # In[ ]: d.name = "Yogi" # In[ ]: a.name # In[ ]: a.mylist[0] = -1 # In[ ]: d.mylist # In[ ]: e = copy.deepcopy(a) # In[ ]: a.__dict__ # In[ ]: del a.logf # In[ ]: e = copy.deepcopy(a) # In[ ]: a.mylist[0] = "a" # In[ ]: e.mylist # In[ ]: Bear.bear_num # `deepcopy`: copies all attributes pointed to internally # # Subclassing & Inheritance # # `class classname(baseclass):` # # For example, # `class Flower(Plant):` # # Here we say that # "the class Flower is a subclass of the base class Plant." # Plant may itself be a subclass of LivingThing # # # attributes of the baseclass are inherited by the subclass # # In[ ]: class Plant: num_known = 0 def __init__(self,common_name,latin_name=None): self.latin_name = latin_name self.common_name = common_name Plant.num_known += 1 # In[ ]: class Flower(Plant): has_pedals = True # In[ ]: p = Plant("poison ivy") e = Flower("poppy") # In[ ]: e.has_pedals # In[ ]: Plant.num_known # In[ ]: Flower.__bases__[0].__name__ # instantiation of a Flower reuses the `__init__` from the Plant class. It also sets has pedals = `True` # In[ ]: class Plant: num_known = 0 def __init__(self,common_name,latin_name=None): self.latin_name = latin_name self.common_name = common_name Plant.num_known += 1 def __str__(self): return "I am a plant (%s)!" % self.common_name class Flower(Plant): has_pedals = True def __str__(self): return "I am a flower (%s)!" % self.common_name # now the `__str__` method of Flower takes precedence over the `__str__` method of the parent class # In[ ]: f = Flower("rose") ; print(f) # In[ ]: p = Plant("oak"); print(p) # In[ ]: class Flower(Plant): has_pedals = True def __init__(self,common_name,npedals=5,pedal_color="red",latin_name=None): ## call the __init__ of the parent class Plant.__init__(self,common_name,latin_name=latin_name) self.npedals = npedals self.pedal_color = pedal_color def __str__(self): return "I am a flower (%s)!" % self.common_name # we can still use the parent class' `__init__` # In[ ]: f = Flower("rose") ; print(f) # In[ ]: class A: def __init__(self): print("A") class B(A): def __init__(self): A.__init__(self) print("B") # In[ ]: a = A() # In[ ]: b = B() # ## Multiple Inheritances # # `class Flower1(Plant,EdibleFood,SmellyStuff)` # # when executing a method the namespace of: # # - `Flower1` is searched first # - `Plant` second (and it's baseclasses...and their baseclasses) # - `EdibleFood` second (and it's baseclasses...and their baseclasses) # - `SmellyStuff` second (and it's baseclasses...and their baseclasses) # ## Errors (& Handling) # # #### There are many different kinds of exceptions that can be raised by an error and each of them can be handled differently... # In[ ]: 3.1415/0 # In[ ]: def this_fails(): x = 3.1415/0 try: this_fails() except ZeroDivisionError as detail: print('Handling run-time error:',detail ) # `import exceptions` # http://docs.python.org/library/exceptions.html # #
# exception BaseException # exception Exception # exception StandardError # exception ArithmeticError # exception LookupError # exception EnvironmentError # exception AssertionError # exception AttributeError # exception EOFError # exception GeneratorExit # exception IOError # exception ImportError # exception IndexError # exception KeyError # exception KeyboardInterrupt # exception MemoryError # exception NameError # exception OverflowError # exception ReferenceError # exception RuntimeError # exception StopIteration # exception SyntaxError # exception SystemError # exception SystemExit # exception TypeError # exception ValueError # exception VMSError # exception WindowsError # exception ZeroDivisionError # exception Warning # exception UserWarning # exception DeprecationWarning # exception PendingDeprecationWarning # exception SyntaxWarning # exception RuntimeWarning # exception FutureWarning # exception ImportWarning ## In[ ]: def divide(x, y): try: result = x / y except ZeroDivisionError: print("division by zero!") else: print("result is", result) finally: print("executing finally clause") # In[ ]: divide(2,1) # In[ ]: divide(2,0) # In[ ]: divide("2","1") # ## Catch Multiple Error Types # In[ ]: get_ipython().run_cell_magic('file', 'catcherr.py', 'import sys\ntry:\n f = open(\'myfile.txt\')\n s = f.readline()\n i = int(s.strip())\nexcept IOError as err:\n print("I/O error: %s" % err)\nexcept ValueError:\n print("Could not convert data to an integer.")\nexcept:\n print("Unexpected error:", sys.exc_info()[0])\n raise\n') # > exc_info(...) # # > exc_info() -> (type, value, traceback) # # Return information about the most recent exception caught by an except # clause in the current stack frame or in an older stack frame. # In[ ]: run catcherr # ## Raising Errors # # we can raise errors in our codes (which themselves might be caught upstream) # In[ ]: a = "cat food" if a != "spam": raise NameError("anything that isn't spam breaks my code") # ## Errors are a family of classes (and subclasses)! #
# BaseException # +-- SystemExit # +-- KeyboardInterrupt # +-- GeneratorExit # +-- Exception # +-- StopIteration # +-- StandardError # | +-- BufferError # | +-- ArithmeticError # | | +-- FloatingPointError # | | +-- OverflowError # | | +-- ZeroDivisionError # | +-- AssertionError # | +-- AttributeError # | +-- EnvironmentError # | | +-- IOError # | | +-- OSError # | | +-- WindowsError (Windows) # | | +-- VMSError (VMS) # | +-- EOFError # | +-- ImportError # | +-- LookupError # | | +-- IndexError # | | +-- KeyError # | +-- MemoryError # | +-- NameError # | | +-- UnboundLocalError # | +-- ReferenceError # | +-- RuntimeError # | | +-- NotImplementedError # | +-- SyntaxError # | | +-- IndentationError # | | +-- TabError # | +-- SystemError # | +-- TypeError # | +-- ValueError # | +-- UnicodeError # | +-- UnicodeDecodeError # | +-- UnicodeEncodeError # | +-- UnicodeTranslateError # +-- Warning # +-- DeprecationWarning # +-- PendingDeprecationWarning # +-- RuntimeWarning # +-- SyntaxWarning # +-- UserWarning # +-- FutureWarning # +-- ImportWarning # +-- UnicodeWarning # +-- BytesWarning ## ## We can create our own exception classes by subclassing others (e.g., Exception) # In[ ]: import datetime # In[ ]: class MyError(StopIteration): def __init__(self,value=None): ## call the baseclass Exception __init__ Exception.__init__(self) self.value = value print("exception with %s at time %s" % (self.value,datetime.datetime.now())) def __str__(self): return "you said %s" % self.value # In[ ]: raise MyError("darnit") # In[ ]: get_ipython().run_line_magic('debug', '') # In[ ]: get_ipython().run_line_magic('pdb', '') # In[ ]: