diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..ba77eac9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.defaultFormatter": "ms-python.python" + }, + "python.formatting.provider": "none" +} \ No newline at end of file diff --git a/art.py b/art.py new file mode 100644 index 00000000..5ca0efc9 --- /dev/null +++ b/art.py @@ -0,0 +1,15 @@ +# art.py + +import sys +import random + +chars = '\|/' + +def draw(rows, columns): + for r in range(rows): + print(''.join(random.choice(chars) for _ in range(columns))) + +if __name__ == '__main__': + if len(sys.argv) != 3: + raise SystemExit("Usage: art.py rows columns") + draw(int(sys.argv[1]), int(sys.argv[2])) \ No newline at end of file diff --git a/closure.py b/closure.py new file mode 100644 index 00000000..aec213f6 --- /dev/null +++ b/closure.py @@ -0,0 +1,58 @@ +# CLOSURE + +# Function that returns another function +# def add(x, y): +# def do_add(): +# print(f'{x} + {y} -> {x + y}') +# return do_add + +# Observe how the inner function refers to variables defined by the other function + +# Futher observe that those variables are somehow kept alive after add() has finished. + +# If an inner function is returned as a 'result', the inner function is known as a 'Closure' + +# Essential feature: A "Closure" retains the values of all variables needed for the function to run properly later on. + +# To make it work, references to the outer variables(bound variables) get carried along with the function. + +# a = add(6, 7) +# print(a.__closure__) +# print(a.__closure__[0].cell_contents) +# print(a.__closure__[1].cell_contents) + +# def sub(x, y): +# result = x - y +# def get_result(): +# return result +# return get_result + +# Closures only capture used variables +# Carefully observe: x and y are not included (not needded in the function body) + +# def counter(n=0): +# def incr(): +# nonlocal n +# n += 1 +# return n +# return incr + +# Closure variables are mutable ( can be declared by nonlocal) +# Can be used to hold mutable internal state, much like object or class + +def counter(value): + def incr(): + nonlocal value + value += 1 + return value + def decr(): + nonlocal value + value -= 1 + return value + return incr, decr + +# Above define two functions that manipulate a value + + + + diff --git a/date.py b/date.py new file mode 100644 index 00000000..8a5ad8e6 --- /dev/null +++ b/date.py @@ -0,0 +1,36 @@ +# date.py + +import time + +class Date: + def __init__(self, year, month, day): + self.year = year + self.month = month + self.day = day + + def __str__(self): + return '%d-%d-%d' % (self.year, self.month, self.day) + + def __repr__(self): + return 'Date(%r,%r,%r)' % (self.year, self.month, self.day) + + @classmethod + def today(cls): + t = time.localtime() + self = cls.__new__(cls) + self.year = t.tm_year + self.month = t.tm_mon + self.day = t.tm_mday + return self + +class Manager: + def __enter__(self): + print('Enterning...') + return self + + def __exit__(self, ty, val, tb): + print('Leaving...') + if ty: + print('An exception occurred.') + + diff --git a/decorator.py b/decorator.py new file mode 100644 index 00000000..e47d44f2 --- /dev/null +++ b/decorator.py @@ -0,0 +1,56 @@ +# A decorator is a function that creates a wrapper around another function + +# The warpper is a new function that works exactly like the original function(same arguments, same return type) except that some kind of extra processing is carried out + +def add(x, y): + return x + y + +def logged(func): + # Define a wrapper function around func + def wrapper(*args, **kwargs): + print('Calling', func.__name__) + return func(*args, **kwargs) + return wrapper + +# When we create a wrapper, we often want to replace the original function with it + +# Other codes continues to use the original function name, but it is unaware that a wrapper has been injected(that's the whole point) + +# When we replace a function with a wrapper, we are usually giving the function extra functionality. This process is known as 'decoration'. We are 'decorating' a function with some extra features. + +# Whenever we see a decorader syntax, just remember that a function is getting wrapped. + +# A decorator that reports execution time +from functools import wraps +import time +def timethis(func): + @wraps(func) + def wrapper(*args, **kwargs): + start = time.time() + r = func(*args, **kwargs) + end = time.time() + print(func.__name__, end - start) + return r + return wrapper + +@timethis +def check(): + ''' + List creation + ''' + l = [] + for i in range(10000000): + l.append(i) + + +# Decorator with Args +# Logging with a custom message +def logmsg(message): + def logged(func): + @wraps(func) + def wrapper(*args, **kwargs): + print(message.format(name=func.__name__)) + return func(*args, **kwargs) + return wrapper + return logged + diff --git a/descrip.py b/descrip.py new file mode 100644 index 00000000..0e37d3df --- /dev/null +++ b/descrip.py @@ -0,0 +1,12 @@ +# descrip.py + +class Descriptor: + def __init__(self, name): + self.name = name + def __get__(self, instance, cls): + print('%s:__get__' % self.name) + def __set__(self, instance, value): + print('%s:__set__ %s' % (self.name, value)) + def __delete__(self, instance): + print('%s:__delete__' % self.name) + \ No newline at end of file diff --git a/future.py b/future.py new file mode 100644 index 00000000..7a676408 --- /dev/null +++ b/future.py @@ -0,0 +1,19 @@ +import time +from concurrent.futures import Future +from threading import Thread + +def worker(x, y): + print('About to work') + time.sleep(20) + print('Done') + return x + y + +# Wrapper around the function to use a future +def do_work(x, y, fut): + fut.set_result(worker(x, y)) + +def caller(): + fut = Future() + Thread(target=do_work, args=(2, 3, fut)).start() + result = fut.result() + print('Got:', result) diff --git a/higher.py b/higher.py new file mode 100644 index 00000000..759c2cd3 --- /dev/null +++ b/higher.py @@ -0,0 +1,25 @@ +def sum_squares(nums): + total = 0 + for n in nums: + total += n ** 2 + return total + +def sum_cubes(nums): + total = 0 + for n in nums: + total += n ** 3 + return total + + +def sum_map(func, nums): + total = 0 + for n in nums: + total += func(n) + return total + +def square(x): + return x * x + +# Lambda function - Anonymous function on the spot +nums = [1,2,3,4] +r = sum_map(lambda x: x*x, nums) \ No newline at end of file diff --git a/logcall.py b/logcall.py new file mode 100644 index 00000000..69126e74 --- /dev/null +++ b/logcall.py @@ -0,0 +1,22 @@ +# logcall.py + +from functools import wraps + +# def logged(func): +# print('Adding logged to', func.__name__) +# @wraps(func) +# def wrapper(*args, **kwargs): +# print('Calling', func.__name__) +# return func(*args, **kwargs) +# return wrapper + + +def logformat(fmt): + def logged(func): + print('Adding logged to', func.__name__) + @wraps(func) + def wrapper(*args, **kwargs): + print(fmt.format(func=func)) + return func(*args, **kwargs) + return wrapper + return logged \ No newline at end of file diff --git a/mapreduce.py b/mapreduce.py new file mode 100644 index 00000000..322600f3 --- /dev/null +++ b/mapreduce.py @@ -0,0 +1,21 @@ +def map(func, values): + result = [] + for x in values: + result.append(func(x)) + return result + +def reduce(func, values, initial=0): + result = initial + for x in values: + result = func(x, result) + return result + +def sum(x, y): + return x + y + +def square(x): + return x * x + +nums = [1, 2, 3, 4] + +result = reduce(sum, map(square, nums)) \ No newline at end of file diff --git a/mutint.py b/mutint.py new file mode 100644 index 00000000..2300d76c --- /dev/null +++ b/mutint.py @@ -0,0 +1,65 @@ +# mutint.py + +from functools import total_ordering + +@total_ordering +class MutInt: + __slot__ = ['value'] + + def __init__(self, value): + self.value = value + + def __str__(self): + return str(self.value) + + def __repr__(self): + return f'MutInt({self.value!r})' + + def __format__(self, fmt): + return format(self.value, fmt) + + def __add__(self, other): + if isinstance(other, MutInt): + return MutInt(self.value + other.value) + elif isinstance(other, int): + return MutInt(self.value + other) + else: + return NotImplemented + + __radd__ = __add__ + + def __iadd__(self, other): + if isinstance(other, MutInt): + self.value += other.value + return self + elif isinstance(other, int): + self.value += other + return self + else: + return NotImplemented + + def __eq__(self, other): + if isinstance(other, MutInt): + return self.value == other.value + elif isinstance(other, int): + return self.value == other + else: + return NotImplemented + + def __lt__(self, other): + if isinstance(other, MutInt): + return self.value < other.value + elif isinstance(other, int): + return self.value < other + else: + return NotImplemented + + def __int__(self): + return self.value + + def __float__(self): + return float(self.value) + + __index__ = __int__ # Make indexing work + + \ No newline at end of file diff --git a/orig_stock.py b/orig_stock.py new file mode 100644 index 00000000..22c3c410 --- /dev/null +++ b/orig_stock.py @@ -0,0 +1,51 @@ +# orig_stock.py + +class Stock: + __slots__ = ('name','_shares','_price') + _types = (str, int, float) + def __init__(self, name, shares, price): + self.name = name + self.shares = shares + self.price = price + + def __repr__(self): + # Note: The !r format code produces the repr() string + return f'{type(self).__name__}({self.name!r}, {self.shares!r}, {self.price!r})' + + def __eq__(self, other): + return isinstance(other, Stock) and ((self.name, self.shares, self.price) == + (other.name, other.shares, other.price)) + + @classmethod + def from_row(cls, row): + values = [func(val) for func, val in zip(cls._types, row)] + return cls(*values) + + @property + def shares(self): + return self._shares + @shares.setter + def shares(self, value): + if not isinstance(value, self._types[1]): + raise TypeError(f'Expected {self._types[1].__name__}') + if value < 0: + raise ValueError('shares must be >= 0') + self._shares = value + + @property + def price(self): + return self._price + @price.setter + def price(self, value): + if not isinstance(value, self._types[2]): + raise TypeError(f'Expected {self._types[2].__name__}') + if value < 0: + raise ValueError('price must be >= 0') + self._price = value + + @property + def cost(self): + return self.shares * self.price + + def sell(self, nshares): + self.shares -= nshares diff --git a/pcost.py b/pcost.py new file mode 100644 index 00000000..a8d84bd3 --- /dev/null +++ b/pcost.py @@ -0,0 +1,34 @@ +# pcost.py + +# total_cost = 0.0 + +# with open('Data/portfolio.dat', 'r') as f: +# for line in f: +# row = line.split() +# nshares = int(row[1]) +# price = float(row[2]) +# total_cost += nshares * price + + +# print('Total Cost:', total_cost) + + +def portfolio_cost(filename): + total_cost = 0.0 + with open(filename, 'r') as f: + for line in f: + row = line.split() + try: + nshares = int(row[1]) + price = float(row[2]) + total_cost += nshares * price + except ValueError as e: + print('Couldn\'t parse:', repr(line)) + print('Reason:', e) + continue + return total_cost + +if __name__ == '__main__': + print(portfolio_cost('Data/portfolio.dat')) + + \ No newline at end of file diff --git a/reader.py b/reader.py new file mode 100644 index 00000000..3f1bd826 --- /dev/null +++ b/reader.py @@ -0,0 +1,99 @@ +# reader.py + +import csv +from collections.abc import Sequence +from stock import Stock +import logging + +logging.basicConfig(level=logging.DEBUG, filename='reader.log') +log = logging.getLogger(__name__) + + + +class DataCollection(Sequence): + def __init__(self, headers): + for col_name in headers: + setattr(self, col_name, []) + self.headers = headers + + def __len__(self): + return len(getattr(self, self.headers[0])) + + def __getitem__(self, index): + if isinstance(index, slice): + new_self = self.__class__(self.headers) + for col_name in self.headers: + setattr(new_self, col_name, getattr(self, col_name)[index]) + return new_self + + data = {col_name:getattr(self, col_name)[index] for col_name in self.headers} + return data + + def append(self, d): + for col_name in self.headers: + getattr(self, col_name).append(d[col_name]) + + +def read_csv_as_columns(filename, types): + with open(filename, 'r') as f: + rows = csv.reader(f) + headers = next(rows) + records = DataCollection(headers) + for row in rows: + record = {name:func(val) for name, func, val in zip(headers, types, row)} + records.append(record) + + return records + +from typing import Iterable + +def convert_csv(lines: Iterable, converter_func, *, headers: list|None = None) -> list: + ''' + Convert lines of CSV data into a list of container defined by converter_func + ''' + rows = csv.reader(lines) + if headers is None: + headers = next(rows) + records = [] + for rowno, row in enumerate(rows, start=1): + try: + record = converter_func(headers, row) + records.append(record) + except ValueError as e: + # print(f"Row {rowno}: Bad row: {repr(row)}") + log.warning(f"Row {rowno}: Bad row: {repr(row)}") + # log.warning('Row %s: Bad row: %s', rowno, row) + # print(f"Error: {e}") + log.debug(f"Row {rowno}: Reason: {repr(row)}") + continue + return records + +def csv_as_dicts(lines: Iterable, types: list, *, headers:list|None = None) ->list[dict]: + ''' + Convert lines of CSV data into a list of dictionaries + ''' + return convert_csv(lines, lambda headers, row: { name: func(val) for name, func, val in zip(headers, types, row)}, headers=headers) + +def csv_as_instances(lines, cls, *, headers=None): + ''' + Convert lines of CSV data into a list of instances + ''' + return convert_csv(lines, lambda headers, row: cls.from_row(row),headers=headers) + +def read_csv_as_dicts(filename: str, types: list, *, headers: list|None = None) -> list[dict]: + ''' + Read CSV data into a list of dictionaries with optional type conversion + ''' + with open(filename, 'rt') as file: + return csv_as_dicts(file, types, headers=headers) + +def read_csv_as_instances(filename: str, cls: Stock, *, headers: list|None = None) -> list[Stock]: + ''' + Read a CSV data into a list of instances + ''' + with open(filename, 'rt') as file: + return csv_as_instances(file, cls, headers=headers) + + + + diff --git a/readport.py b/readport.py new file mode 100644 index 00000000..6d38448c --- /dev/null +++ b/readport.py @@ -0,0 +1,20 @@ +# readport.py + +import csv + +# A function that reads a file into a list of dicts + + +def read_portfolio(filename): + portfolio = [] + with open(filename) as f: + rows = csv.reader(f) + headers = next(rows) + for row in rows: + record = { + 'name': row[0], + 'shares': int(row[1]), + 'price': float(row[2]) + } + portfolio.append(record) + return portfolio diff --git a/readrides.py b/readrides.py new file mode 100644 index 00000000..3c577b67 --- /dev/null +++ b/readrides.py @@ -0,0 +1,138 @@ +# readrides.py + +import csv + +def read_rides_as_tuples(filename): + ''' + Read the bus ride data as a list of tuples + ''' + records = [] + with open(filename) as f: + rows = csv.reader(f) + headings = next(rows) # Skip headers + for row in rows: + route = row[0] + date = row[1] + daytype = row[2] + rides = int(row[3]) + record = (route, date, daytype, rides) + records.append(record) + return records + +def read_rides_as_dicts(filename): + ''' + Read the bus ride data as a list of dicts + ''' + records = RideData() # <---- CHANGED + with open(filename) as f: + rows = csv.reader(f) + heading = next(rows) # Skip headers + for row in rows: + route = row[0] + date = row[1] + daytype = row[2] + rides = int(row[3]) + record = { + 'route': route, + 'date': date, + 'daytype': daytype, + 'rides': rides + } + records.append(record) + return records + +class Row: + # Slot class, uncomment this to use slot + # __slots__ = ('route', 'date', 'daytype', 'rides') + def __init__(self, route, date, dattype, rides): + self.route = route + self.date = date + self.daytype = dattype + self.rides = rides + +# Named Tuples, uncomment this to use named tuples +# from collections import namedtuple +# Row = namedtuple('Row', ['route', 'date', 'daytype', 'rides']) + +def read_rides_as_instances(filename): + ''' + Read the bus ride data as a list of instances + ''' + records = [] + with open(filename) as f: + rows = csv.reader(f) + heading = next(rows) # Skip headers + for row in rows: + route = row[0] + date = row[1] + daytype = row[2] + rides = int(row[3]) + record = Row(route, date, daytype, rides) + records.append(record) + return records + +def read_rides_as_columns(filename): + ''' + Read the bus ride data into 4 lists, representing columns + ''' + routes = [] + dates = [] + daytypes = [] + numrides = [] + with open(filename) as f: + rows = csv.reader(f) + headings = next(rows) # Skip headers + for row in rows: + routes.append(row[0]) + dates.append(row[1]) + daytypes.append(row[2]) + numrides.append(row[3]) + + return dict(routes=routes, dates=dates, daytypes=daytypes, numrides=numrides) + +# The great "fake" +from collections.abc import Sequence +class RideData(Sequence): + def __init__(self): + # Each value is a list with all the values( a column) + self.routes = [] # Columns + self.dates = [] + self.dattypes = [] + self.numrides = [] + + def __len__(self): + # All lists assumed to have the same length + return len(self.routes) + + def __getitem__(self, index): + # If index is given as slice + if isinstance(index, slice): + new_self = self.__class__() + new_self.routes = self.routes[index] + new_self.dates = self.dates[index] + new_self.dattypes = self.dattypes[index] + new_self.numrides = self.numrides[index] + return new_self + return { + 'route': self.routes[index], + 'date': self.dates[index], + 'daytype': self.dattypes[index], + 'rides': self.numrides[index] } + + def append(self, d): + self.routes.append(d['route']) + self.dates.append(d['date']) + self.dattypes.append(d['daytype']) + self.numrides.append(d['rides']) + + + +if __name__ == '__main__': + import tracemalloc + tracemalloc.start() + read_rides = read_rides_as_dicts + rows = read_rides('Data/ctabus.csv') + + print('Memory Use: Current %d, Peak %d' % tracemalloc.get_traced_memory()) + + # slot < tuple < namedtuple < class row < dict \ No newline at end of file diff --git a/sample.py b/sample.py new file mode 100644 index 00000000..03beab65 --- /dev/null +++ b/sample.py @@ -0,0 +1,30 @@ +# sample.py + +from logcall import logformat + + +logged = logformat('{func.__code__.co_filename}:{func.__name__}') + +@logged +def mul(x,y): + return x * y + +class Spam: + @logged + def instance_method(self): + pass + + @classmethod + @logged + def class_method(cls): + pass + + @staticmethod + @logged + def static_method(): + pass + + @property + @logged + def property_method(self): + pass \ No newline at end of file diff --git a/simple.py b/simple.py new file mode 100644 index 00000000..d14c881c --- /dev/null +++ b/simple.py @@ -0,0 +1,30 @@ +# simple.py + +# def add(x, y): +# ''' +# Add x and y. +# ''' +# # assert isinstance(x , int) +# # assert isinstance(y, int) +# return x + y + +# Assertions are meant to check user inputs + +# Should validate program invarients(internal conditions that must always hold true) + +# Failure indicates a programming error and assign blame(e.g., to the caller) + +# Can be disabled (python -O) + + +from validate import Integer, validated + +@validated +def add(x: Integer, y: Integer) -> Integer: + return x + y + +@validated +def pow(x: Integer, y: Integer) -> Integer: + return x ** y + + diff --git a/stock.py b/stock.py new file mode 100644 index 00000000..de077936 --- /dev/null +++ b/stock.py @@ -0,0 +1,20 @@ +# stock.py + +from structure import Structure + +from validate import validated, PositiveInteger + +class Stock(Structure): + _fields = ('name', 'shares', 'price') + + @property + def cost(self): + return self.shares * self.price + + @validated + def sell(self, nshares: PositiveInteger): + self.shares -= nshares + +Stock.create_init() + + \ No newline at end of file diff --git a/structure.py b/structure.py new file mode 100644 index 00000000..fcabefb5 --- /dev/null +++ b/structure.py @@ -0,0 +1,23 @@ +# structure.py + +class Structure: + _fields = tuple() + + def __repr__(self): + return f"{type(self).__name__}({', '.join(repr(getattr(self, name)) for name in self._fields)})" + + def __setattr__(self, name, value): + if name.startswith('_') or name in self._fields: + super().__setattr__(name, value) + else: + raise AttributeError('No attribute %s' % name) + + @classmethod + def create_init(cls): + argstr = ', '.join(cls._fields) + init_code = f'def __init__(self, {argstr}):\n' + for name in cls._fields: + init_code += f' self.{name} = {name}\n' + locs = { } + exec(init_code, locs) + cls.__init__ = locs['__init__'] diff --git a/tableformat.py b/tableformat.py new file mode 100644 index 00000000..d030eb4f --- /dev/null +++ b/tableformat.py @@ -0,0 +1,97 @@ +# tableformat.py + +# def print_table(sequence, headers): +# for column in headers: +# print('%10s ' % (column), end='') +# print() + +# print(('-'*10 + ' ')*len(headers)) + +# for item in sequence: +# for column in headers: +# print('%10s ' % (getattr(item, column)), end='') +# print() + + +# def print_table(records, fields): +# print(' '.join('%10s' % fieldname for fieldname in fields)) +# print(('-'*10 + ' ')*len(fields)) +# for record in records: +# print(' '.join('%10s' % getattr(record, fieldname) for fieldname in fields)) + +from abc import ABC, abstractmethod + +class FormatError(Exception): + pass + +class TableFormatter(ABC): + @abstractmethod + def headings(self, headers): + pass + + @abstractmethod + def row(self, rowdata): + pass + +class TextTableFormatter(TableFormatter): + def headings(self, headers): + print(' '.join('%10s' % h for h in headers)) + print(('-'*10 + ' ')*len(headers)) + + def row(self, rowdata): + print(' '.join('%10s' % d for d in rowdata)) + +class CSVTableFormatter(TableFormatter): + def headings(self, headers): + print(','.join('%s' % h for h in headers)) + + def row(self, rowdata): + print(','.join('%s' % d for d in rowdata)) + +class HTMLTableFormatter(TableFormatter): + def headings(self, headers): + print('