diff --git a/session28_ModernPython/Chainmap.py b/session28_ModernPython/Chainmap.py new file mode 100644 index 0000000..a3a62c7 --- /dev/null +++ b/session28_ModernPython/Chainmap.py @@ -0,0 +1,11 @@ +from collections import ChainMap + +d1 = {'a': 1, 'b': 2} +d2 = {'b': 3, 'c': 4} +d3 = {**d1, **d2} +chain = ChainMap(d1, d2) + +print(chain) + +for key, value in chain.items(): + print(key, value) diff --git a/session28_ModernPython/Combinatorics.py b/session28_ModernPython/Combinatorics.py new file mode 100644 index 0000000..148f540 --- /dev/null +++ b/session28_ModernPython/Combinatorics.py @@ -0,0 +1,14 @@ +import itertools + +abc = 'ABC' +print('permutations:') +for p in itertools.permutations(abc): + print(p) + +print('with no repetition') +for c in itertools.combinations(abc, 2): + print(c) + +print('with replacement:') +for c in itertools.combinations_with_replacement(abc, 2): + print(c) diff --git a/session28_ModernPython/ContextManager.py b/session28_ModernPython/ContextManager.py new file mode 100644 index 0000000..0ba3a0a --- /dev/null +++ b/session28_ModernPython/ContextManager.py @@ -0,0 +1,84 @@ +from contextlib import contextmanager +import sys + + +# Where does a context manager make sense? + +# Setup/Preperation: +f = open('./text.txt', 'w') + +# Do the stuff you want to do: +f.write('text') + +# Clean up so everything is nice and tidy: +f.close() + + +# Context Manager as Class +class File(object): + def __init__(self, file_name, method): + self.file_obj = open(file_name, method) + + def __enter__(self): + return self.file_obj + + def __exit__(self, type, value, traceback): + print("Exception has been handled") + self.file_obj.close() + return True + + +@contextmanager +def ContextFile(filename, method): + try: + file_obj = open(filename, method) + yield file_obj + except Exception: + print("Exception has been handled") + finally: + file_obj.close() + return True + + +# Context Manager as Generator +@contextmanager +def open_file(name, method): + f = open(name, method) + yield f + f.close() + + +@contextmanager +def redirect_stdout(fileobj): + oldstdout = sys.stdout + sys.stdout = fileobj + try: + yield fileobj + finally: + sys.stout = oldstdout + + +with ContextFile('demo.txt', 'w') as opened_file: + opened_file.undefined_function() + +print('This goes to the console') +with open('./ContextManager.py', 'a') as f: + with redirect_stdout(f): + print('# This goes down there:') + + +# Really nice stuff: +@contextmanager +def ignored(*exceptions): + try: + yield + except exceptions: + pass + + +@ignored(ZeroDivisionError) +def do_something_dangerous(): + 1 / 0 + +do_something_dangerous() +# This goes down there: diff --git a/session28_ModernPython/CustomSortOrder.py b/session28_ModernPython/CustomSortOrder.py new file mode 100644 index 0000000..976b3e5 --- /dev/null +++ b/session28_ModernPython/CustomSortOrder.py @@ -0,0 +1,18 @@ +colors = ['mauve', 'taupe', 'teal', 'razzmatazz', 'gamboge'] + +# old way: Comparison Functions + + +def compare_length(c1, c2): + if len(c1) < len(c2): + return -1 + if len(c1) > len(c2): + return 1 + return 0 + +# doesn't work anymore in Python3 +# print(sorted(colors, cmp=compare_length)) + + +# new way: Key functions +print(sorted(colors, key=len)) diff --git a/session28_ModernPython/Dataclass.py b/session28_ModernPython/Dataclass.py new file mode 100644 index 0000000..f9aa9cc --- /dev/null +++ b/session28_ModernPython/Dataclass.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass + + +@dataclass( + init=True, # generated __init__() + repr=True, # generates __reps__() + eq=True, # generates __eq__(), this compares the class as a tuple of + # fields but also checks if "other" is of the same type + order=False, # generates __lt__(), __le__(), __gt__(), and __ge__() + unsafe_hash=False, # not unsafe, just not very friendly. + # If False, __hash__() is set according + # to eq and frozen: + # eq & frozen: __hash__() will be generated + # eq & !frozen: __hash__() = None + # !eq: __hash__() is left untouched and only inherited + # unsafe_hash=True should be used if the class is + # logically immutable but can nonetheless be mutated + frozen=False # Assigning to fields raises an exception +) +class Point: + x: int + y: int + + diff --git a/session28_ModernPython/DataclassCards.py b/session28_ModernPython/DataclassCards.py new file mode 100644 index 0000000..ca8bdc8 --- /dev/null +++ b/session28_ModernPython/DataclassCards.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass, field +from typing import List +from random import sample + + +@dataclass(order=True) +class PlayingCard: + rank: str + suit: str + + def __post_init__(self): + self.sort_index = (RANKS.index(self.rank) * len(SUITS) + + SUITS.index(self.suit)) + + def __str__(self): + return f'{self.suit}{self.rank}' + + +RANKS = '2 3 4 5 6 7 8 9 10 J Q K A'.split() +SUITS = '♣ ♢ ♡ ♠'.split() + + +def make_french_deck(): + return [PlayingCard(r, s) for s in SUITS for r in RANKS] + + +@dataclass +class Deck: + cards: List[PlayingCard] = field(default_factory=make_french_deck) + + def __repr__(self): + cards = ', '.join(f'{c!s}' for c in self.cards) + return f'{self.__class__.__name__}({cards})' + + def shuffle(self): + self.cards = sample(self.cards, k=len(self.cards)) + + def sort(self): + self.cards = sorted(self.cards) + + +d = Deck() +print(d) +d.sort() +print(d) +d.shuffle() +print(d) diff --git a/session28_ModernPython/DataclassEmployeeExample.py b/session28_ModernPython/DataclassEmployeeExample.py new file mode 100644 index 0000000..b488cc4 --- /dev/null +++ b/session28_ModernPython/DataclassEmployeeExample.py @@ -0,0 +1,138 @@ +from dataclasses import dataclass, field, fields +from datetime import datetime +from pprint import pprint + + +@dataclass(order=True, unsafe_hash=True) +class Employee: + emp_id: int + name: str + gender: str + salary: int = field(hash=False, repr=False, metadata={"units": "bitcoin"}) + age: int = field(hash=False) + viewed_by: list = field(default_factory=list, compare=False, repr=False) + + def access(self, viewer_id): + self.viewed_by.append((viewer_id, datetime.now())) + + +e1 = Employee(emp_id='3536465054', + name='Rachel Hettinger', + gender='female', + salary=22, + age=0x30, + ) + +e2 = Employee(emp_id='4054646353', + name='Martin Murchison', + gender='male', + salary=20, + age=0x30, + ) + +e1.access('Roger Wastun') +e1.access('Shelly Summers') +pprint(e1.viewed_by) +pprint(sorted([e1, e2])) +assignments = {e1: 'gather requirements', e2: 'write tests'} +pprint(assignments) +fields(e1)[3] +""" +""" + + +# Generated Code: +""" + + +from dataclasses import _HAS_DEFAULT_FACTORY + + +def __init__( + self, + emp_id: int, + name: str, + gender: str, + salary: int, + age: int, + viewed_by: list = _HAS_DEFAULT_FACTORY, +) -> None: + self.emp_id = emp_id + self.name = name + self.gender = gender + self.salary = salary + self.age = age + self.viewed_by = list() if viewed_by is _HAS_DEFAULT_FACTORY else viewed_by + + +def __repr__(self): + return ( + self.__class__.__qualname__ + + f"(emp_id={self.emp_id!r}, name={self.name!r}, gender={self.gender!r}, age={self.age!r})" + ) + + +def __eq__(self, other): + if other.__class__ is self.__class__: + return (self.emp_id, self.name, self.gender, self.salary, self.age) == ( + other.emp_id, + other.name, + other.gender, + other.salary, + other.age, + ) + return NotImplemented + + +def __lt__(self, other): + if other.__class__ is self.__class__: + return (self.emp_id, self.name, self.gender, self.salary, self.age) < ( + other.emp_id, + other.name, + other.gender, + other.salary, + other.age, + ) + return NotImplemented + + +def __le__(self, other): + if other.__class__ is self.__class__: + return (self.emp_id, self.name, self.gender, self.salary, self.age) <= ( + other.emp_id, + other.name, + other.gender, + other.salary, + other.age, + ) + return NotImplemented + + +def __gt__(self, other): + if other.__class__ is self.__class__: + return (self.emp_id, self.name, self.gender, self.salary, self.age) > ( + other.emp_id, + other.name, + other.gender, + other.salary, + other.age, + ) + return NotImplemented + + +def __ge__(self, other): + if other.__class__ is self.__class__: + return (self.emp_id, self.name, self.gender, self.salary, self.age) >= ( + other.emp_id, + other.name, + other.gender, + other.salary, + other.age, + ) + return NotImplemented + + +def __hash__(self): + return hash((self.emp_id, self.name, self.gender)) + +""" diff --git a/session28_ModernPython/Decorator.py b/session28_ModernPython/Decorator.py new file mode 100644 index 0000000..e2948cc --- /dev/null +++ b/session28_ModernPython/Decorator.py @@ -0,0 +1,19 @@ +import functools + + +def do_twice(func): + @functools.wraps(func) + def wrapper_do_twice(*args, **kwargs): + print('here I could do something beforehand') + func(*args, **kwargs) + func(*args, **kwargs) + print('here I could do something beforehand') + return wrapper_do_twice + + +@do_twice +def print_hello(name=''): + print(f"hello {name}") + + +print(print_hello.__name__) diff --git a/session28_ModernPython/DefaultDict.py b/session28_ModernPython/DefaultDict.py new file mode 100644 index 0000000..efedc96 --- /dev/null +++ b/session28_ModernPython/DefaultDict.py @@ -0,0 +1,72 @@ +from collections import defaultdict +from pprint import pprint + +Pizza_Ingredients = [ + 'tomatoes', + 'flour', + 'water', + 'mozzarella', + 'pineapple', + 'tomatoes', + 'paella', +] + + +# typical dict-stuff: +def oldschool_count(): + count = {} + for ingredient in Pizza_Ingredients: + if ingredient not in count.keys(): + count[ingredient] = 0 + count[ingredient] += 1 + pprint(count) + + +def oldschool_cluster(): + length = {} + for ingredient in Pizza_Ingredients: + l = len(ingredient) + try: + length[l].append(ingredient) + except KeyError: + length[l] = [ingredient] + pprint(length) + + +# A bit nicer: +def count_via_get(): + count = {} + for ingredient in Pizza_Ingredients: + count[ingredient] = count.get(ingredient, 0) + 1 + pprint(count) + + +# now with neat defaultdicts: +def defaultdict_count(): + count = defaultdict(int) + for ingredient in Pizza_Ingredients: + count[ingredient] += 1 + pprint(dict(count)) + + +def defaultdict_cluster(): + length = defaultdict(list) + for ingredient in Pizza_Ingredients: + length[len(ingredient)].append(ingredient) + pprint(dict(length)) + +# defaultdict can take arbitrary generators: +def f(): + return 2 + + +d = defaultdict(f) +d[1] = 2 +d[2] +print(dict(d)) + +d2 = defaultdict(lambda: defaultdict(list)) +d2[1][2].append(3) +print(dict(d2)) +""" +""" \ No newline at end of file diff --git a/session28_ModernPython/Deque.py b/session28_ModernPython/Deque.py new file mode 100644 index 0000000..1ec0376 --- /dev/null +++ b/session28_ModernPython/Deque.py @@ -0,0 +1,14 @@ +from collections import deque + +colors = ['mauve', 'taupe', 'teal', 'razzmatazz', 'gamboge'] + + +del colors[0] +colors.pop(0) +colors.insert(0, 'amaranth') + +colors = deque(colors) + +del colors[0] +colors.popleft() +colors.appendleft('amaranth') diff --git a/session28_ModernPython/Generator.py b/session28_ModernPython/Generator.py new file mode 100644 index 0000000..6badf14 --- /dev/null +++ b/session28_ModernPython/Generator.py @@ -0,0 +1,58 @@ +from random import randint, choice +Pizza_Ingredients = [ + 'tomatoes', + 'flour', + 'water', + 'yeast', + 'mozzarella', + 'pineapple', + 'paella', + 'mushrooms', + 'spinach (frozen)', + 'kiwi', + 'mashed potatoes', + 'banana' +] +stuff_i_do_not_like = [ + 'water', + 'flour' +] + + +def safe_food(ingredients, leave_out): + for ingredient in ingredients: + if ingredient not in leave_out: + yield ingredient + + +def adapted_recipe(ingredients, leave_out): + units = ['stone', 'yard', 'torr^2/horsepower', 'morgen', 'handful', 'metric decipound'] + for ingredient in safe_food(ingredients, leave_out): + yield f"{randint(1, 5)} {choice(units)} {ingredient}" + +""" + +for item in adapted_recipe( + ingredients=Pizza_Ingredients, + leave_out=stuff_i_do_not_like, +): + print(item) + + +""" +def adapted_recipe_hungry(ingredients, leave_out): + units = ['stone', 'yard', 'torr^2/horsepower', 'morgen', 'handful', 'metric decipound'] + hunger = 0 + for ingredient in safe_food(ingredients, leave_out): + hunger = yield f"{randint(1, 5) + hunger} {choice(units)} {ingredient}" + + +recipe_generator = adapted_recipe_hungry( + Pizza_Ingredients, + stuff_i_do_not_like) +recipe_generator.send(None) +for hunger in range(len(Pizza_Ingredients)): + try: + print(recipe_generator.send(hunger)) + except(StopIteration): + break diff --git a/session28_ModernPython/GeneratorExpression.py b/session28_ModernPython/GeneratorExpression.py new file mode 100644 index 0000000..4202031 --- /dev/null +++ b/session28_ModernPython/GeneratorExpression.py @@ -0,0 +1,8 @@ +s = sum([i**2 for i in range(3)]) +print(s) + +s = sum(i**2 for i in range(3)) +print(s) + +d = dict((i, i % 7) for i in range(12)) +print(d) diff --git a/session28_ModernPython/IterSentinel.py b/session28_ModernPython/IterSentinel.py new file mode 100644 index 0000000..116c51c --- /dev/null +++ b/session28_ModernPython/IterSentinel.py @@ -0,0 +1,20 @@ +from functools import partial +# ugly: +with open('./IterSentinel.py', 'r') as f: + blocks = [] + while True: + block = f.read(3) + if block == '###': + break + blocks.append(block) +print(blocks) + +###### + + +# nice: +with open('./IterSentinel.py', 'r') as f: + blocks = [] + for block in iter(partial(f.read, 3), '###'): + blocks.append(block) +print(blocks) diff --git a/session28_ModernPython/NamedTuple.py b/session28_ModernPython/NamedTuple.py new file mode 100644 index 0000000..a4df672 --- /dev/null +++ b/session28_ModernPython/NamedTuple.py @@ -0,0 +1,32 @@ +from collections import namedtuple +from itertools import zip_longest + + +def analyse_string(string): + qs = string.count('q') + ingredients = sorted(list(set(string))) + d_p = sum((ord(s_c) - ord(s_p))**2 for s_c, s_p in zip_longest( + string, + 'pineapple', + fillvalue=chr(0))) + return (qs, ingredients, d_p) + + +def sensible_analyse_string(string): + Analysis = namedtuple('Analysis', [ + 'number_of_qs', + 'sorted_letters', + 'pineapple_distance' + ]) + qs = string.count('q') + ingredients = sorted(list(set(string))) + d_p = sum((ord(s_c) - ord(s_p))**2 for s_c, s_p in zip_longest( + string, + 'pineapple', + fillvalue=chr(0))) + return Analysis(qs, ingredients, d_p) + + +print(analyse_string(string='kumquat')) +b = sensible_analyse_string('pineapple').number_of_qs +print(b) diff --git a/session28_ModernPython/Partial.py b/session28_ModernPython/Partial.py new file mode 100644 index 0000000..c46b53b --- /dev/null +++ b/session28_ModernPython/Partial.py @@ -0,0 +1,12 @@ +from functools import partial + + +def power(base, exponent): + return base ** exponent + + +def square(base): + return power(base, 2) + + +square = partial(power, exponent=2) diff --git a/session28_ModernPython/ShorthandTernaray.py b/session28_ModernPython/ShorthandTernaray.py new file mode 100644 index 0000000..4e196e8 --- /dev/null +++ b/session28_ModernPython/ShorthandTernaray.py @@ -0,0 +1,12 @@ +# Old Ternary +this_is_True = True +print('a' if this_is_True else 'b') + +# New Ternary +print('' or 'not_empty') +print('not_empty' or '') +print(0 or 1) +print(1 or 0) +print('True' or 'Gemüse') +print(False or 'Obst') +print(False or 0) diff --git a/session28_ModernPython/TryExceptElseFinally.py b/session28_ModernPython/TryExceptElseFinally.py new file mode 100644 index 0000000..7fa0e9a --- /dev/null +++ b/session28_ModernPython/TryExceptElseFinally.py @@ -0,0 +1,16 @@ +try: + # The Code you care about + print('I really want to print this line!') + pass +except OSError as e: + # The things to do if stuff goes wrong one way + print('Oh no a OSError occurred:', e) +except KeyError as e: + # The things to do if stuff goes wrong another way + print('Where are my Keys?:', e) +else: + # this will be executed if no exceptions are raised + print('Luckily nothing bad happend!') +finally: + # This will be executed regardless whether exceptions are raised + print('This will be printed in any case.') diff --git a/session28_ModernPython/Walrus.py b/session28_ModernPython/Walrus.py new file mode 100644 index 0000000..51393bd --- /dev/null +++ b/session28_ModernPython/Walrus.py @@ -0,0 +1,4 @@ +l = [1, 2, 3, 4, 5] +if (n := len(l)) > 3: + print(n) + diff --git a/session28_ModernPython/for_else.py b/session28_ModernPython/for_else.py new file mode 100644 index 0000000..ab331e7 --- /dev/null +++ b/session28_ModernPython/for_else.py @@ -0,0 +1,18 @@ +def find_oldschool(seq, target): + found = False + for i, value in enumerate(seq): + if value == target: + found = True + break + if not found: + return -1 + return i + + +def find_with_else(seq, target): + for i, value in enumerate(seq): + if value == target: + break + else: + return -1 + return i diff --git a/session28_ModernPython/slots.py b/session28_ModernPython/slots.py new file mode 100644 index 0000000..ad2ed11 --- /dev/null +++ b/session28_ModernPython/slots.py @@ -0,0 +1,13 @@ +class SomeClass(object): + __slots__ = ['name', 'identifier'] + + def __init__(self, name, identifier): + self.name = name + self.identifier = identifier + + +a = SomeClass(name='Moritz', identifier=25255) +try: + a.PunkBandMembership = True +except AttributeError as e: + print(e)