# problem 1
class Foo:
    x = 1

f1 = Foo()
f2 = Foo()
print Foo.x, f1.x, f2.x

f2.x = 2
print Foo.x, f1.x, f2.x

Foo.x = 3
print Foo.x, f1.x, f2.x

# problem 4
class A:
    x = 1
    def f(self):
        return self.x

class B(A):
    x = 2
    def g(self):
        return self.x

a = A()
b = B()

print a.f()
print b.f(), b.g()

# problem 7
x = 1

class Foo:
    a = x
    x = 2
    print a, x

print x

%%file numbers.txt
one
two
three
four
five

def wordcount(fileobj):
    lc = len(fileobj.readlines())
    fileobj.seek(0)
    wc = len(fileobj.read().split())
    fileobj.seek(0)
    cc = len(fileobj.read())
    return lc, wc, cc

print wordcount(open("numbers.txt"))

class FakeFile:
    def read(self):
        return "one\ntwo\nthree\n"
    def readlines(self):
        return ["one\n", "two\n", "three\n"]
    def seek(self, pos):
        pass

print wordcount(FakeFile())

class Foo: pass
f = Foo()

f.read = lambda: ""
f.readlines = lambda: []
f.seek = lambda n: 0
print wordcount(f)
    

from StringIO import StringIO
f = StringIO()
f.write("hello\nworld\n")
f.seek(0)
print f.read()

f.seek(0)
print wordcount(f)

x = 1
print x

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "(%s, %s)" % (self.x, self.y)

p = Point(2, 3)
print p

print repr(1)

print repr("hello")

print "hello"

[1, 2, "3, 4"]

print p

print [p]

print repr(p)

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "(%s, %s)" % (self.x, self.y)
    
    def __repr__(self):
        return "Point(%s, %s)" % (self.x, self.y)

p = Point(2, 3)
print p, [p, "hello", (2, 3), 5]

# Do you know what happens when you do this?

x = [1, 2, 3, 4]
print x[1]

x.__getitem__(1)

x = xrange(2, 8)
print len(x)
print x[3]

x.__len__()

class yrange:
    def __init__(self, start, stop):
        self.start = start
        self.stop = stop
    
    def __len__(self):
        return self.stop - self.start
        
    def __getitem__(self, index):
        return self.start + index

y = yrange(2, 8)
print len(y)
print y[3]

## Command library using classes

%%file command1.py
class Command:
    def run(self, filenames):
        lines = self.readfiles(filenames)
        lines = self.generate_output(lines)
        self.printlines(lines)
        
    def process_line(self, line):
        """All bases classes should implement this."""
        raise NotImplementedError()

    def generate_output(self, lines):
        for line in lines:
            for outline in self.process_line(line):
                yield outline

    def readfiles(self, filenames):
        for f in filenames:
            for line in open(f):
                yield line

    def printlines(self, lines):
        for line in lines:
            print line.strip("\n")
            


%%file uppercase1.py
from command1 import Command

class UpperCase(Command):
    def process_line(self, line):
        yield line.upper()

if __name__ == "__main__":
    import sys
    cmd = UpperCase()
    cmd.run(sys.argv[1:])

!python uppercase1.py uppercase1.py

%%file grep2.py
from command1 import Command

class Grep(Command):
    def __init__(self, pattern):
        self.pattern = pattern
        
    def process_line(self, line):
        if self.pattern in line:
            yield line
            
if __name__ == "__main__":
    import sys
    cmd = Grep(sys.argv[1])
    cmd.run(sys.argv[2:])

!python grep2.py def grep2.py

%%file command2.py
"""A new approach to writing Command class.
"""

class Command:
    def run(self, filenames):
        for filename in filenames:
            self.process_file(filename)
            
    def process_file(self, filename):
        for line in open(filename):
            process_line(line)

    def process_line(self, line):
        """All bases classes should implement this."""
        raise NotImplementedError()

class UpperCase(Command):
    def process_line(self, line):
        print line.upper()
        
class WordCount(Command):
    def process_file(self, filename):
        lc = len(open(filename).readlines())
        wc = len(open(filename).read().split())
        cc = len(open(filename).read())
        print lc, wc, cc, filename
        
cmd = WordCount()
cmd.run(["command1.py", "command2.py"])

!python command2.py

class Foo:
    x = 1
    def getx(self):
        return self.x
    
foo = Foo()
print foo.getx()

print Foo.getx(foo)

Foo.getx

foo.getx

def add(x, y):
    return x+y

def make_adder(x):
    def adder(y):
        return add(x, y)
    return adder

add5 = make_adder(5)
print add5(6)

def bindself(method, self):
    """Returns a new function with self already bound"""
    def f(*a, **kw):
        return method(self, *a, **kw)
    return f

f = bindself(Foo.getx, foo)
f()

class Foo:
    x = 1
    
    def getx(self):
        return self.x

foo = Foo()

Foo

dir(Foo)

Foo.__dict__

Foo.__dict__['x'] = 2
Foo.x

Foo.__dict__['y'] = 3
Foo.y

# Lets try to find how add5 is storing value of x
dir(add5)

def inc(x, amount=1): return x+amount

inc.func_defaults

x = 1 # what does this mean?

g = globals()

g['x']

g['x'] = 2

x

def add(x, y): return x+y

add(5, 4)

d = [1, 2, 3]
d[2]

Foo

Foo()

class Adder:
    def __init__(self, x):
        self.x = x
        
    def __call__(self, y):
        return add(self.x, y)
        
add5 = Adder(5)
print add5(6)

class Foo(object):
    x = 1
foo = Foo()

foo.x = 1
foo.x

getattr(foo, "x")

class Attr:
    def __getattr__(self, name):
        return name.upper()

a = Attr()
a.x

a.y

a.foo

getattr(a, "x")

x = 1
type(x)

type(int)

class Foo: pass

type(Foo)

type(file)

class Foo(object): pass

type(Foo)

class Foo(object): 
    def __len__(self): return 4

foo = Foo()
len(foo)

foo.__len__ = lambda: 5

len(foo)

class Bar:
    def __len__(self): return 4

bar = Bar()
len(bar)

bar.__len__ = lambda: 5

len(bar)

class Bar:
    x = 1
    def getx(self): return self.x
        
bar = Bar()
print bar.getx()

# What does this mean? bar.getx()
# f = bar.getx
# f()

# What what does x[1]

class Person(object):
    firstname = "Foo"
    lastname = "Bar"
    _phonenumber = "0"

    @property    
    def fullname(self):
        return self.firstname + " " + self.lastname

    @property
    def phone(self):
        return self._phonenumber
        
    @phone.setter
    def phone(self, value):
        if len(value) != 10:
            raise ValueError("Invalid Phone number")
        self._phonenumber = value
        
    #phone = property(_get_phone, _set_phone)
        
p = Person()
print p.fullname
print p.phone
p.phone = "1234567890"
print p.phone

dir(Person)

Person.phone

p.phone

p.firstname

p.__dict__

Person.__dict__

Person.phone.__get__(p)

class SimpleDescriptor(object):
    def __get__(self, obj, type=None):
        if obj is None:
            return self
        else:
            return 1

class Foo:
    x = SimpleDescriptor()

Foo.x

foo = Foo()
foo.x

class my_property(object):
    def __init__(self, getter):
        self.getter = getter
        
    def __get__(self, obj, type=None):
        if obj is None:
            return self
        else:
            return self.getter(obj)
        
class Person:
    @my_property
    def fullname(self):
        print "fulname called"
        return "Foo Bar"

p = Person()
print Person.fullname
print p.fullname
print p.fullname

class lazy_property(object):
    def __init__(self, getter):
        self.getter = getter
        
    def __get__(self, obj, type=None):
        if obj is None:
            return self
        value = self.getter(obj)
        obj.__dict__[self.getter.__name__] = value
        return value
        
class Person:
    def __init__(self, first, last):
        self.first = first
        self.last = last
        
    @lazy_property
    def fullname(self):
        print "fulname called"
        return self.first + " " + self.last

p = Person("Foo", "Bar")
print Person.fullname
print p.__dict__
print p.fullname
print p.__dict__
print p.fullname        

#p = Person("Foo", "Bar2")
#print p.fullname

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

p = Point(1, 2)
p.__dict__

class Point2(object):
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

p2 = Point2(1, 2)

p2.__dict__