Python Training at VMWare Pune - Day 3

Sep 27-29, 2017 Vikrant Patil

These notes are available online at http://notes.pipal.in/2017/vmware-pune-advpy

© Pipal Academy LLP

Day 1 | Day 2 | Day 3

Understanding Names , Scope and Namespaces

In [1]:
x = 2 + 3

Names or alias is just a mechanism to get handle to computed or created object

In [3]:
%%file moldule.py
value = 42
novalue = 24
Writing moldule.py
In [4]:
%%file anothermodule.py
value = 0
novalue = "No value"
Writing anothermodule.py
In [6]:
import moldule
In [7]:
import anothermodule
In [10]:
moldule.value
Out[10]:
42
In [11]:
anothermodule.value
Out[11]:
0
In [12]:
del moldule.value
In [13]:
anothermodule.value
Out[13]:
0
In [14]:
moldule.value
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-14-fa992ab4575f> in <module>()
----> 1 moldule.value

AttributeError: module 'moldule' has no attribute 'value'
In [15]:
x
Out[15]:
5

Namespace

Namespace is mapping from names to objects.

  • Names from two different namespaces are by no way related. This means you can easily define attributes of same name in two different namespaces.
  • Namespaces are created at different moments and they have different lifespans

Scope

In [16]:
amount = 10000
balance = 50000

def withdraw(balance, amount):
    balance = balance - amount
    return balance

fixdep = withdraw(balance, 1000)

print("balance = ", balance)
print("amount = ", amount)
balance =  50000
amount =  10000
In [22]:
%%file scopetest.py

hello = "Hello Initial"
def scope_changed():
    hello = "Hello from scope_changed"
    def change_local():
        hello = "hello local"
        
    def change_nonlocal():
        hello = "Hello initial inside change_local"
        print(hello)
        def f():
            nonlocal hello
            hello = "hello nonlocal"
        f()
        print(hello)
    
    def change_global():
        global hello
        hello = "Hello global"
        
    print("Before change_local", hello)
    change_local()
    print("After change_local", hello)
    print("Before change_nonlocal", hello)
    change_local()
    print("After change_nonlocal", hello)
    print("Before change_global", hello)
    change_local()
    print("After change_global", hello)
    
    
print("Initial global hello :", hello)
scope_changed()
print("After :", hello)
Overwriting scopetest.py
In [23]:
!python scopetest.py
Initial global hello : Hello Initial
Before change_local Hello from scope_changed
After change_local Hello from scope_changed
Before change_nonlocal Hello from scope_changed
Hello initial inside change_local
hello nonlocal
After change_nonlocal Hello from scope_changed
Before change_global Hello from scope_changed
After change_global Hello from scope_changed
After : Hello global

Problem : What will be output?

def f(x):
    x.append(5)

def g(x):
    x = [1,1,,1,1]
l = [1,2,3,4]
f(l)
print(l)
g(l)
print(l)

Classes

Classses in python look like this by syntax

 class Name:
     <statement1>
     <statement2>
     .
     .
     .
In [24]:
import math

class Circle:
    def __init__(self, radius):
        self.radius = radius
        
    def area(self):
        return math.pi*self.radius**2
    
class Square:
    def __init__(self, s):
        self.s = s
        
    def area(self):
        return self.s*self.s
    
class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height
In [25]:
shapes = [Circle(1), Square(1), Circle(2), Square(2)]
In [26]:
areas = [shape.area() for shape in shapes ]
In [27]:
areas
Out[27]:
[3.141592653589793, 1, 12.566370614359172, 4]
In [28]:
shapes.append(Triangle(1, 1))
In [29]:
areas = [shape.area() for shape in shapes ]
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-29-ba262c09c1fa> in <module>()
----> 1 areas = [shape.area() for shape in shapes ]

<ipython-input-29-ba262c09c1fa> in <listcomp>(.0)
----> 1 areas = [shape.area() for shape in shapes ]

AttributeError: 'Triangle' object has no attribute 'area'
In [30]:
t = [type(s) for s in shapes]
In [31]:
t
Out[31]:
[__main__.Circle,
 __main__.Square,
 __main__.Circle,
 __main__.Square,
 __main__.Triangle]
In [32]:
isinstance(shapes[0], Circle)
Out[32]:
True
In [33]:
isinstance(shapes[0], Triangle)
Out[33]:
False

This is called as duck-typing... It looks like duck and qwacks like duck so it must be duck!

python works of atrributes rather types. It will just access the atrribute and if it is not there it will throw exception!

Why classes

In [34]:
%%file bank0.py

balance = 0

def deposite(amount):
    global balance
    balance += amount

def withdraw(amount):
    global balance
    balance -= amount
    
def get_balance():
     return balance

def main():
    deposite(100)
    withdraw(40)
    print(get_balance())
    deposite(20)
    
    print(get_balance())
    
    
if __name__ == "__main__":
    main()
Writing bank0.py
In [35]:
!python bank0.py 
60
80
In [37]:
%%file bank1.py

def make_account():
    return {"balance":0}

def deposite(account, amount):
    account['balance'] += amount

def withdraw(account, amount):
    account['balance'] -= amount
    
def get_balance(account):
     return account['balance']

def main():
    a1 = make_account()
    a2 = make_account()
    
    deposite(a1, 100)
    withdraw(a1, 40)
    deposite(a2, 200)
    withdraw(a2, 50)
    print(get_balance(a1))
    print(get_balance(a2))
    
    
if __name__ == "__main__":
    main()
Overwriting bank1.py
In [38]:
!python bank1.py
60
150
In [43]:
%%file bank2.py

class BankAccount:
    def __init__(self):
        self.balance = 0
        
    def deposite(self, amount):
        self.balance += amount
        
    def withdraw(self, amount):
        self.balance -= amount
        
    def get_balance(self):
        return self.balance
    

def main():
    a1 = BankAccount()
    a2 = BankAccount()
    
    a1.deposite(100)
    a1.withdraw(20)
    
    print(a1.get_balance())
    
    
if __name__ == "__main__":
    main()
Overwriting bank2.py
In [44]:
!python bank2.py
80
  • functions manipulate data nicely. while classes model data nicely.
  • The purpose of class is to bundle a data structure which represnts logical entity with the operations that work with data structure
  • one big advantage of using OOP is extensibility
In [46]:
class A:
    pass

Example: Text Formatting

In [45]:
%%file five.txt
one
two
three
four
five
Writing five.txt
In [47]:
class Formatter:
    
    def format_text(self, text):
        """
        Formats the given text
        This implementation just returns same text back.
        but sub classes should implement this method to
        provide different kinds of formatting
        """
        return text
    
    def format_file(self, filename):
        text = open(filename).read()
        return self.format_text(text)
In [48]:
class UpperCaseFormatter(Formatter):
    
    def format_text(self, text):
        return text.upper()
In [49]:
f = UpperCaseFormatter()
In [51]:
print(f.format_text("hello"))
HELLO
In [52]:
print(f.format_file("five.txt"))
ONE
TWO
THREE
FOUR
FIVE
In [54]:
class LineFormatter(Formatter):
    
    def format_line(self, line):
        return line
    
    def format_text(self, text):
        lines  = text.splitlines()
        lines = [self.format_line(l) for l in lines]
        return "\n".join(lines)
In [58]:
class PrefixFormatter(LineFormatter):
    def __init__(self, prefix):
        self.prefix = prefix
        
    def format_line(self, line):
        return self.prefix + line
In [59]:
f = PrefixFormatter(prefix="[INFO]: ")
In [60]:
print(f.format_text("Hello\nworld"))
[INFO]: Hello
[INFO]: world
In [61]:
print(f.format_file("five.txt"))
[INFO]: one
[INFO]: two
[INFO]: three
[INFO]: four
[INFO]: five

Class Object

In [62]:
class ClassA:
    value = 42
    
    def f(self):
        return "Hello from ClassA"
In [63]:
ClassA
Out[63]:
__main__.ClassA
In [64]:
ClassA.value
Out[64]:
42
In [65]:
ClassA.f
Out[65]:
<function __main__.ClassA.f>

Instance Object

In [67]:
x = ClassA()
In [68]:
x
Out[68]:
<__main__.ClassA at 0x7f0a243b82e8>
In [69]:
x.value
Out[69]:
42
In [70]:
x.my_own_value = 43
In [71]:
x.f
Out[71]:
<bound method ClassA.f of <__main__.ClassA object at 0x7f0a243b82e8>>
In [72]:
ClassA.f()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-72-46724427a1a8> in <module>()
----> 1 ClassA.f()

TypeError: f() missing 1 required positional argument: 'self'
In [73]:
x.f()
Out[73]:
'Hello from ClassA'
In [74]:
ClassA.f(x)
Out[74]:
'Hello from ClassA'
In [75]:
x.another_func = lambda x:x*x
In [76]:
method = x.f
In [77]:
method
Out[77]:
<bound method ClassA.f of <__main__.ClassA object at 0x7f0a243b82e8>>
In [78]:
method()
Out[78]:
'Hello from ClassA'

Customizing classes

str() and repr()

In [79]:
x = (1,2)
In [80]:
str(x)
Out[80]:
'(1, 2)'
In [81]:
repr(x)
Out[81]:
'(1, 2)'
In [82]:
(1, 2)
Out[82]:
(1, 2)
In [95]:
class Pair:
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "({0}, {1})".format(str(self.x), str(self.y)) 
        
    def __repr__(self):
        return "Pair({0.x!r}, {0.y!r})".format(self)
        # 0.x -> x attribute from 0th argument
        # repr(x.0) -> 0.x!r  for str use 0.x!s
In [96]:
p = Pair(2,3)
In [97]:
p
Out[97]:
Pair(2, 3)
In [98]:
print(p)
(2, 3)

arithmatic operators

In [100]:
class Pair:
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "({0}, {1})".format(str(self.x), str(self.y)) 
        
    def __repr__(self):
        return "Pair({0.x!r}, {0.y!r})".format(self)
    
    def __add__(self, p):
        return Pair(self.x+p.x,  self.y + p.y)
    
    def __sub__(self, p):
        return Pair(self.x - p.x,  self.y - p.y)
    
    def __rmul__(self, c):
        return Pair(self.x*c , self.y*c)
    
    def __mul__(self, c):
        return Pair(self.x*c , self.y*c)
    
    def __eq__(self, p):
        return self.x==p.x and self.y==p.y
In [101]:
p1 = Pair(1,2)
In [102]:
p2 = Pair(4,5)
In [103]:
p1 + p2 
Out[103]:
Pair(5, 7)
In [104]:
p2 - p1
Out[104]:
Pair(3, 3)
In [105]:
print(p2 - p1)
(3, 3)
In [106]:
p1*3
Out[106]:
Pair(3, 6)
In [107]:
3 * p1
Out[107]:
Pair(3, 6)
In [108]:
p1 == p2
Out[108]:
False
In [109]:
p1 == Pair(1,2)
Out[109]:
True
In [110]:
p1['z']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-110-de46436dab54> in <module>()
----> 1 p1['z']

TypeError: 'Pair' object is not subscriptable

[] operator

In [112]:
class Pair:
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __str__(self):
        return "({0}, {1})".format(str(self.x), str(self.y)) 
        
    def __repr__(self):
        return "Pair({0.x!r}, {0.y!r})".format(self)
    
    def __add__(self, p):
        return Pair(self.x+p.x,  self.y + p.y)
    
    def __sub__(self, p):
        return Pair(self.x - p.x,  self.y - p.y)
    
    def __rmul__(self, c):
        return Pair(self.x*c , self.y*c)
    
    def __mul__(self, c):
        return Pair(self.x*c , self.y*c)
    
    def __eq__(self, p):
        return self.x==p.x and self.y==p.y
    
    def __getitem__(self, name):
        return self.__dict__[name]
    
    def __setitem__(self, name, value):
        if name in ["x", "y"]:
            self.__dict__[name] = value
        else:
            raise Exception("Only x, y values can be set")
In [113]:
http://localhost:8888/notebooks/day3.ipynb#p = Pair(2, 3)
In [114]:
p['x'] = 1
In [115]:
p
Out[115]:
Pair(1, 3)
In [116]:
p['z'] = 3
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-116-08ac67d1852c> in <module>()
----> 1 p['z'] = 3

<ipython-input-112-997e8d20c496> in __setitem__(self, name, value)
     33             self.__dict__[name] = value
     34         else:
---> 35             raise Exception("Only x, y values can be set")

Exception: Only x, y values can be set
In [117]:
p.z = 30
In [118]:
p.z = 30

Q: Is it possible to make class immutable?

In [120]:
class UpperCase:
    
    def __getattr__(self, name):
        return name.upper()
In [121]:
u = UpperCase()
In [122]:
u.name
Out[122]:
'NAME'
In [127]:
class Sealed:
    
    def __setattr__(self, name, value):
        raise Exception("No chance!")
In [124]:
s = Sealed()
In [125]:
s.x = 3
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-125-442c8444ae31> in <module>()
----> 1 s.x = 3

<ipython-input-123-dc9c5c1ffbfb> in __setattr__(self, name, value)
      2 
      3     def __setattr__(self, name, value):
----> 4         raise Exception("No chance!")

Exception: No chance!
In [126]:
s['x'] = 3
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-126-4f1a95f72cff> in <module>()
----> 1 s['x'] = 3

TypeError: 'Sealed' object does not support item assignment
In [128]:
class Date:
    __slots__ =['year', 'month', 'day']
    
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
In [130]:
d = Date(2017, 10, 12)
In [131]:
d.x  = 60
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-131-dcffaf43ef03> in <module>()
----> 1 d.x  = 60

AttributeError: 'Date' object has no attribute 'x'

problem Write a class Timer to measure time taken by some task. The class should have start and stop methods. and shooul be able to get time elapsed between start and stop.

use time.time()

t = Timer()
t.start()
do_some_stuff()
t.stop()
print(t.get_time_taken())
In [132]:
import time

class Timer:
    def __init__(self):
        self._start = 0
        self._end = 0
        
    def start(self):
        self._start = time.time()
        
    def stop(self):
        self._end = time.time()
        
    def get_time(self):
        return self._end - self._start
    
t = Timer()
t.start()
for i in range(100000):
    i*i*i
t.stop()
print(t.get_time())
0.03272557258605957

Properties

In [152]:
class Person:
    def __init__(self, name, email):
        self._name = name
        self._email = email
     
    #getter
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError("Expected String")
        self._name = value
        
    @name.deleter
    def name(self):
        raise AttributeError("Can't delete atrribute name")
    
    
In [153]:
p = Person("Alice", "alice@wonder.land")
In [154]:
p.name
Out[154]:
'Alice'
In [155]:
p.name = "Alex"
In [156]:
p.name = 42
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-156-7e1a4a1cfe0b> in <module>()
----> 1 p.name = 42

<ipython-input-152-1cddcb45b325> in name(self, value)
     12     def name(self, value):
     13         if not isinstance(value, str):
---> 14             raise TypeError("Expected String")
     15         self._name = value
     16 

TypeError: Expected String
In [157]:
p.name = "Hatter"
In [158]:
del p.name
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-158-82a48009a62c> in <module>()
----> 1 del p.name

<ipython-input-152-1cddcb45b325> in name(self)
     17     @name.deleter
     18     def name(self):
---> 19         raise AttributeError("Can't delete atrribute name")
     20 
     21 

AttributeError: Can't delete atrribute name
In [159]:
del p._email
In [160]:
del p._name 
In [161]:
p.name
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-161-1c57ed665d7c> in <module>()
----> 1 p.name

<ipython-input-152-1cddcb45b325> in name(self)
      7     @property
      8     def name(self):
----> 9         return self._name
     10 
     11     @name.setter

AttributeError: 'Person' object has no attribute '_name'

Descriptors

In [162]:
class Integer:
    def __init__(self, name):
        self.name = name
        
        
    def __get__(self, instance, cls):
        print("__get__ from", self)
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]
        
    def __set__(self, instance, value):
        print("__set__ from", self)
        if not isinstance(value, int):
            raise TypeError("Expected an int")
        instance.__dict__[self.name] = value
        
    def __delete__(self, instance):
        print("__del__ from ", self)
        del instance.__dict__[self.name]
        
    def __str__(self):
        return "Integer<{0.name!s}>".format(self)
In [163]:
class Point:
    x = Integer('x')
    y = Integer('y')
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
In [164]:
p = Point(2, 3)
__set__ from Integer<x>
__set__ from Integer<y>
In [165]:
Point.__dict__
Out[165]:
mappingproxy({'__dict__': <attribute '__dict__' of 'Point' objects>,
              '__doc__': None,
              '__init__': <function __main__.Point.__init__>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Point' objects>,
              'x': <__main__.Integer at 0x7f0a24162278>,
              'y': <__main__.Integer at 0x7f0a24162a90>})
In [166]:
p.x = 3
__set__ from Integer<x>
In [167]:
p.y = "3"
__set__ from Integer<y>
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-167-ed6b0524c329> in <module>()
----> 1 p.y = "3"

<ipython-input-162-15b3aec98f61> in __set__(self, instance, value)
     14         print("__set__ from", self)
     15         if not isinstance(value, int):
---> 16             raise TypeError("Expected an int")
     17         instance.__dict__[self.name] = value
     18 

TypeError: Expected an int

problem: Implement a my_property decorator that works like built in property

In [172]:
class my_property:
    
    def __init__(self, func):
        self.func = func
        
    def __get__(self, instance, cls):
        if instance is None:
            return self
        print("my_property __get__")
        return self.func(instance)
In [173]:
class Person:
    
    def __init__(self, name, email):
        self._name = name
        self._email = email
        
    @my_property
    def name(self):
        return self._name
In [174]:
p = Person("Alice", "alice@wonder.land")
In [175]:
p.name
my_property __get__
Out[175]:
'Alice'

staticmethod and classmethod

In [176]:
class Person(object):
    def __init__(self, firstname, lastname):
        self.firtname = firstname
        self.lastname = lastname
        
    @staticmethod
    def parse(fullname):
        first, last = fullname.split(" ")
        return Person(first, last)
In [177]:
Person.__dict__
Out[177]:
mappingproxy({'__dict__': <attribute '__dict__' of 'Person' objects>,
              '__doc__': None,
              '__init__': <function __main__.Person.__init__>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Person' objects>,
              'parse': <staticmethod at 0x7f0a2411d278>})
In [179]:
import datetime
class MydateTime(datetime.datetime):
    pass
In [180]:
datetime.datetime.now()
Out[180]:
datetime.datetime(2017, 9, 29, 14, 58, 37, 804674)
In [181]:
MydateTime.now()
Out[181]:
MydateTime(2017, 9, 29, 14, 58, 52, 374750)
In [184]:
class A(object):
    def __init__(self, x):
        self.x = x
        
    @staticmethod
    def parse1(value):
        return A(int(value))
    
    @classmethod
    def parse2(cls, value):
        return cls(value)
    
    
class B(A):
    pass
In [185]:
B.parse1(3)
Out[185]:
<__main__.A at 0x7f0a24169d30>
In [186]:
B.parse2(3)
Out[186]:
<__main__.B at 0x7f0a241544e0>

Context Management Protocol

In [187]:
f = open("context.txt", "w")
f.write("one\n")
f.write("two\n")
f.write("three\n")
Out[187]:
6
In [188]:
f.close()
In [190]:
with open("context.txt", "w") as f:
    f.write("one\n")
    f.write("two\n")
    f.write("three\n")
In [ ]:
 
In [192]:
from socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
    def __init__(self, address, family=AF_INET, type_=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type_
        self.sock = None
        
    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError("Already connected")
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock
    
    def __exit__(self, exception_type, exception_value, traceback):
        self.sock.close()
        self.sock = None
In [194]:
from functools import partial
conn = LazyConnection(("www.python.org", 80))
In [195]:
with conn as s:
    #conn.__enter__() executes : connection open
    s.send(b'GET /index.html HTTP/1.0\r\n')
    s.send(b'Host: www.pyhton.org\r\n')
    s.send(b'\r\n')
    resp = b''.join(iter(partial(s.recv, 8192), b''))
#conn.__exit__() gets executed
    
print(resp.decode('utf-8'))
HTTP/1.1 500 Domain Not Found
Server: Varnish
Retry-After: 0
content-type: text/html
Cache-Control: private, no-cache
connection: keep-alive
X-Served-By: cache-hhn1548-HHN
Content-Length: 248
Accept-Ranges: bytes
Date: Fri, 29 Sep 2017 09:47:06 GMT
Via: 1.1 varnish
Connection: close


<html>
<head>
<title>Fastly error: unknown domain www.pyhton.org</title>
</head>
<body>
<p>Fastly error: unknown domain: www.pyhton.org. Please check that this domain has been added to a service.</p>
<p>Details: cache-hhn1548-HHN</p></body></html>

Problem: Write a class ContextTimer which extends from our previous Timer class. This new class should implement context-management protocol. Where in when you enter in **with** block timer starts fresh, and when you exit **with** block, timer stops. and prints value of time taken within with block

    t = ContextTimer()
    with t as timer:
        for i in range(1000):
            for j in range(1000):
                s = i*j*1.0

    print(t.get_time_taken())
In [196]:
class ContextTimer(Timer):
    
    def reset(self):
        self._start = 0
        self._stop = 0
        
    def __enter__(self):
        self.reset()
        self.start()
        return self
    
    def __exit__(self, exc_ty, exc_val, tb):
        self.stop()
        print("Time taken in with block: ", self.get_time())
In [199]:
with ContextTimer() as t:
    for i in range(1000):
        for j in range(1000):
            s = i*j*1.0
            
    doom
Time taken in with block:  0.23354220390319824
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-199-401d3ff7469c> in <module>()
      4             s = i*j*1.0
      5 
----> 6     doom

NameError: name 'doom' is not defined

Calling methods from class

In [205]:
class Base:
    def __init__(self):
        print("Base.__init__")
        
class A(Base):
    def __init__(self):
        Base.__init__(self)
        print("A.__init__()")
        
class B(Base):
    def __init__(self):
        Base.__init__(self)
        print("B.__init__()")
        
class C(A,B):
    def __init__(self):
        #A.__init__(self)
        #B.__init__(self)
        super().__init__()
        print("C.__init__()")
In [206]:
C.__mro__
Out[206]:
(__main__.C, __main__.A, __main__.B, __main__.Base, object)

Starting and stopping threads

In [207]:
from threading import Thread, Event
import time

def task(n , event):
    while (not event.is_set()) and n>0:
        print("T-minus", n)
        n -= 1
        time.sleep(5)
    print("Stopping Bye..")

e = Event()
t = Thread(target=task, args=(10, e))
t.start()
T-minus 10
T-minus 9
T-minus 8
In [208]:
e.set()
Stopping Bye..

References:

  • Concurrency from grouind up - David beazley
  • python 3 meta programming - slides and video by david beazley
  • python cookbook - 3rd edition
  • Structure and Interpretation of Computer Programs - SICP
In [ ]: