

class Point:
   x = 0
   y = 0
   def __init__(self,x,y):
        self.x = x
        self.y = y
   def jump(self,x,y):
        self.x += x
        self.y += y

#--------------------------------------------------

class ColorPoint(Point):
   color = "white"
   def __init__(self,x,y,color):
        self.x = x
        self.y = y
        self.color = color


ColorPoint.x

ColorPoint.y

colorPoint.color

p = ColorPoint(0,0,"red")

p.jump(5,5)

p.x

p.y

q = ColorPoint(10,20,"blue")

q.jump(100,200)

q.x

q.y

#--------------------------------------------------


class Counter:
    def __init__(self,n):
        self.ctr = n
    def next(self):
        self.ctr += 1
        return self.ctr

#--------------------------------------------------
    
def counted(C):
  class CC(C):
    idx = 0
    def __init__(self,*args):
      try: 
	C.__init__(self,*args)
      except: pass
      CC.idx += 1
      self.inst_idx = CC.idx
  return CC

#--------------------------------------------------

CPoint = counted(Point)

p1 = CPoint(0,0)
p2 = CPoint(1,10)
p3 = CPoint(100,100)
p4 = CPoint(25,45)

p2.inst_idx #2nd instance
p3.inst_idx #3rd instance

CPoint.idx  #total number of instances so far





CCounter = counted(Counter)
c1 = CCounter(10)
# c1 is indeed a counter...
for i in range(10):print c1.next()
c2 = CCounter(0)
c3 = CCounter(-1992)
c5 = CCounter(10)
# c2,c3,c4,c5 are all counters
for i in range(10):
    print c2.next(),c3.next(),c4.next(),c5.next()


c5.inst_idx  #fifth instance
c1.inst_idx  #first instance
CCounter.idx #total number of instances

#---------------------------------------------------

#but suppose you have a recursively defined class:

class ListCell:
    def __init__(self,n):
        self.data = 0
        if n > 1: self.next = ListCell(n-1)
        else: self.next = None
    def __str__(self):
        if self.next: rest = " --> " + str(self.next)
        else: rest = ""
        return str(self.data) + rest

CListCell = counted(ListCell)

l = CListCell(5)
str(l)
l.data = 1
l.next.data = 2
l.next.next.data = 3
str(l)

#but we see that:
CListCell.idx

#is just 1

#instead if we did:
ListCell = counted(ListCell)

l = ListCell(10)
l.data = "a"
l.next.data = "b"
l.next.next.data = "c"
str(l)
ListCell.idx

m = ListCell(120)
n = ListCell(25)

n.inst_idx

ListCell.idx

#and we can tell for each cell, when was it created
n.next.next.next.inst_idx
#-------------------------------------------------
#can do this with ANY class

class Point:
    def __init__(self,x,y):
        self.x = x
        self.y = y
        
Point=counted(Point)



class Counter:
    def __init__(self,n):
        self.ctr = n
    def next(self):
        self.ctr += 1
        return self.ctr

Counter=counted(Counter)


class ListCell:
    def __init__(self,n):
        self.data = 0
        if n > 1: self.next = ListCell(n-1)
        else: self.next = None
    def __str__(self):
        if self.next: rest = " --> " + str(self.next)
        else: rest = ""
        return str(self.data) + rest

ListCell = counted(ListCell)

#----------------------------------------------------------
    

def logged(f):
    def g(*args):
        g.__name__ = f.__name__
        print "Call ", f.__name__ ," args: ",str(args)
        rv = f(*args)
        print f.__name__ + " Returns ", str(rv)
        return rv
    return g

#--------------------------------------------------
@logged
def linecount(fname):
    f = open(fname,"r")
    c = 0
    for l in f: c += 1
    f.close()
    return c

linecount = logged(linecount)

linecount("lec15.py")


@logged
def fac(n):
    if n <= 0: return 1
    else: return n*fac(n-1)


fac = logged(fac)
fac(5)

@logged
def kthline(fname,k):
    f = open(fname,"r")
    for i in range(k-1): f.readline()
    return f.readline()

kthline("lec25.py",10)
kthline("lec25",1000)

#---------------------------------------------------------

def checkargtype(id,type):
  def decorator(f):
    def decorated(*args):
      if not(isinstance(args[id],type)):    
        raise Exception("Arg %d (%s) not of type %s" % (id,repr(args[id]),repr(type)))
      return f(*args)
    decorated.__name__ = f.__name__
    return decorated
  return decorator


@checkargtype(1,int)
@checkargtype(0,str)
def kthline(fname,k):
    f = open(fname,"r")
    for i in range(k-1): f.readline()
    return f.readline()

kthline("lec25","a")
kthline(25,"a")

#---------------------------------------------------------

class Profiled(object):
    def __init__(self,f):
        self.count=0
        self.__f=f
        self.__name__=f.__name__
    def __call__(self,*args):
        self.count += 1
        return self.__f(*args)


#---------------------------------------------------------
@Profiled
def fac(n):
    if n <= 0: return 1
    else: return n*fac(n-1)

@memo
def fib(n):
   if n < 1:
      return 1
   else:
      return fib(n-1) + fib(n-2)

#the same as saying
#fac = Profiled(fac)

fac(5)
fac(10)
fac(4)
fac.count

#---------------------------------------------------------
#can compose decorators
@Profiled
@logged
def fac(n):
    if n <= 0: return 1
    else: return n*fac(n-1)





@Profiled
@checkargtype(0,int)
def fac(n):
    if n <= 0: return 1
    else: return n*fac(n-1)


#same as:
#fac = Profiled(checkargtype(0,int)(fac))
#remember that checkargtype(0,int) returns a function

fac(5)
fac(10)
fac.count
fac("jhala")
