<style> .present { text-align: left; } img[alt=knight_moves] { width: 400px; } </style> # More Built-ins, Comprehensions, and Classes (Archive) ## Week 17 Day 4 --- ## Part 1: More Built-ins --- ### Lecture Videos (22 minutes) Watch: - Built-Ins: All And Any (8:00) - Built-Ins: Filter and Map (10:32) --- ### Built-in functions (`all()`) The `all()` function checks that *all* items in a collection are true (a.k.a, that it contains *no falsey* items). ```python= test1 = {"item", "truthy", ""} test2 = [] test3 = [[]] print(all(test1), test1) print(all(test2), test2) print(all(test3), test3) ``` --- ### Built-in functions (`all()`) Answers: ```python= test1 = {"item", "truthy", ""} test2 = [] test3 = [[]] print(all(test1), test1) # False {"", "truthy", "item"} print(all(test2), test2) # True [] print(all(test3), test3) # False [[]] ``` --- ### Built-in functions (`any()`) The `any()` checks that there is at least one truthy item in the provided collection. ```python= test1 = ["item", [], []] test2 = [] test3 = [[]] print(any(test1), test1) print(any(test2), test2) print(any(test3), test3) ``` --- ### Built-in functions (`any()`) Answers: ```python= test1 = ["item", [], []] test2 = [] test3 = [[]] print(any(test1), test1) # True ['item', [], []] print(any(test2), test2) # False [] print(any(test3), test3) # False [[]] ``` --- ### Built-ins (`filter()`) The `filter()` function takes a function and an iterable, and it returns a "filter object". The returned collection includes only the items which, when the function parameter was applied to them, returned a truthy value. ```python= def is_a(num): if num >= 90: return True else: return False scores = [90, 86, 75, 91, 62, 99, 88, 90] only_as = filter(is_a, scores) # does not mutate original print(only_as) # <filter object at 0x10546ad30> print(list(only_as)) # [90, 91, 99, 90] ``` --- ### Built-ins (`filter()`) `filter`'s function parameter can also be defined in line as a `lambda` function. ```python= scores = [90, 86, 75, 91, 62, 99, 88, 90] only_as = filter(lambda num: num >= 90, scores) print(only_as) # <filter object at 0x10546ad30> print(list(only_as)) # [90, 91, 99, 90] ``` --- ### Built-ins (`map()`) The `map()` function takes a function and an iterable, and it returns a "map object" that transforms each value from the original iterable, according to the provided function. ```python= def get_grade(num): if (num >= 90): return "A" elif (num <90 and num >= 80): return "B" elif (num < 80 and num >= 70): return "C" elif (num < 70 and num >= 60): return "D" else: return "F" scores = [90, 86, 75, 91, 62, 99, 88, 90] print(map(get_grade, scores)) # <map object at 0x106faffa0> grades = list(map(get_grade, scores)) print(grades) # ['A', 'B', 'C', 'A', 'D', 'A', 'B', 'A'] ``` --- ### Built-ins (`zip()`) The `zip()` function takes two iterables and returns a "zip object" that pairs values at corresponding indices. You can convert the "zip object" into a list of tuples with the `list()` function. ```python= scores = [90, 86, 75, 91, 62, 99, 88, 90] grades = ["A", "B", "C", "A", "D", "A", "B", "A"] combined = zip(scores, grades) combined_list = list(combined) combined_dict = dict(combined_list) print(combined) # <zip object at 0x1023a9600> print(combined_list) # [(90, 'A'), (86, 'B'), (75, 'C'), (91, 'A'), (62, 'D'), (99, 'A'), (88, 'B'), (90, 'A')] print(combined_dict) # {90: 'A', 86: 'B', 75: 'C', 91: 'A', 62: 'D', 99: 'A', 88: 'B'} ``` --- ## Part 2: Comprehensions --- ### Lecture videos (22 minutes) Watch: - List Comprehensions Demo (18:50) --- ### Comprehensions Comprehensions are composed of an expression followed by a `for...in` statement, followed by an optional `if` clause. They can be used to create new lists (or other mutable sequence types). ```python= my_list = [expression for member in iterable] # with optional if statement my_list = [expression for member in iterable if condition] ``` --- ### Copying a list With a `for` loop: ```python= my_list = [1, "2", "three", True, None] my_list_copy = [] # for loop # ---------- # / \ for item in my_list: my_list_copy.append(item) # | # var print(my_list_copy) # [1, '2', 'three', True, None] ``` --- ### Copying a list With a list comprehension: ```python= my_list = [1, "2", "three", True, None] # var for loop # | ------------- # | / \ my_list_copy = [item for item in my_list] print(my_list_copy) # [1, '2', 'three', True, None] ``` --- ### Mapping over a list with comprehensions Include the desired expression before the `for` statement ```python= my_list = ["jerry", "MARY", "carrie", "larry"] # expression for loop # | ------------- # | / \ mapped_list = [item.lower() for item in my_list] print(mapped_list) # ['jerry', 'mary', 'carrie', 'larry'] ``` --- ### Convert `map()` to list comprehension ```python= nums = [-5, 11, 10, 14] mapped_nums = map(lambda num: num * 2 + 1, nums) print(list(mapped_nums)) # [-9, 23, 21, 29] ``` --- ### Convert `map()` to list comprehension Answer: ```python= nums = [-5, 11, 10, 14] # mapped_nums = map(lambda num: num * 2 +1, nums) mapped_nums = [num * 2 + 1 for num in nums] print(mapped_nums) # [-9, 23, 21, 29] ``` --- ### Filtering a list with comprehensions ```python= nums = [-5, 11, 10, 14] filtered_nums = filter(lambda num: num > 0, nums) print(list(filtered_nums)) # [11, 10, 14] ``` --- ### Filtering a list with comprehensions Answer: ```python= nums = [-5, 11, 10, 14] # filtered_nums = filter(lambda num: num > 0, nums) filtered_nums = [num for num in nums if num > 0] print(filtered_nums) # [11, 10, 14] ``` --- ### Nested loops Nested for loop: ```python= letters = ["a", "b", "c"] nums = [1, 2] new_list = [] # outer loop # ---------- # / \ for l in letters: for n in nums: # <- inner loop new_list.append((l, n)) # \ / # --- # expression print(new_list) # [('a', 1), ('a', 2), ('b', 1), ('b', 2), ('c', 1), ('c', 2)] ``` --- ### Nested loops (list comprehension) With list comprehension—note that the outer loop is first: ```python= letters = ["a", "b", "c"] nums = [1, 2] # expression outer loop inner loop # --- -------------- ----------- # / \ / \ / \ new_list = [(l, n) for l in letters for n in nums] print(new_list) # [('a', 1), ('a', 2), ('b', 1), ('b', 2), ('c', 1), ('c', 2)] ``` --- ### Dictionary comprehensions Use a colon in the expression to separate the key and value. ```python= # a dictionary that maps numbers to the square of the number number_dict = {num: num**2 for num in range(5)} print(number_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} ``` --- ## Part 3: Classes --- ## Lecture videos (23 minutes) Watch: - Classes in Python Demo (19:18) --- ### Classes To create a class we use the `class` keyword, and by convention, we capitalize the names of classes. ```python= # python example class Widget: # more code to come ``` Looks a lot like JavaScript ```javascript= // javascript comparison class Widget { // more code to come } ``` --- ### Initializing classes Generally speaking: - Python's `__init__()` is like JavaScript's `constructor()` - Python's `self` is like JavaScript's `this` ```python= # python example class Widget: def __init__(self): # more code to follow ``` ```javascript= // javascript comparison class Widget { constructor() { // more code to follow } } ``` --- ### the `__init__()` method Python's constructor method is called `__init__()`. ```python= # python example class Widget: def __init__(self, color, shape): self.color = color self.shape = shape ``` ```javascript= // javascript comparison class Widget { constructor(color, shape) { this.color = color; this.shape = shape; } } ``` --- ### Instance variables and methods You can set attributes on the instance with dot notation (`self.some_attribute = value`). You can add methods to the class by defining functions and passing in `self`. ```python= class Widget: def __init__(self, color, shape): self.color = color self.shape = shape def my_method(self, word): print(f"hello {word}") return ``` --- ### Wait, what _is_ `self`? `self` refers to the instance that a method was called on. Whenever you invoke a method on an instance of a class, it is as though you are invoking the class's own method, and passing in the instance as an argument. ```python= some_widget = Widget("blue", "square") # both below do the same thing some_widget.my_method("other argument") Widget.my_method(some_widget, "other argument") ``` --- ### Wait, what _is_ `self`? Calling the first parameter `self` is a convention. This is technically valid Python, and it would do the same thing as calling it `self`. But no one would write it this way. ```python= class Widget: def __init__(banana, color, shape): banana.color = color banana.shape = shape ``` --- ### Instances of classes We create instances of a class by invoking the class as though it is a function (this invokes the class's `__init__()` method) ```python= # in python my_new_widget = Widget("blue", "circle") ``` ```javascript= // javascript comparison const myNewWidget = new Widget("blue", "circle"); ``` --- ### Inheritance To inherit from another class, we pass a reference to that class as an argument in the class definition. We can use the `super()` function to get a reference to the parent class, then invoke the desired function. ```python= class Widget: def __init__(self, color, shape): self.color = color self.shape = shape class Gadget(Widget): def __init__(self, color, shape, noise): super().__init__(color, shape) self.noise = noise thingie = Gadget("purple", "spiral", "whrrrrrr") print(thingie) # <__main__.Gadget object at 0x105103d60> print(thingie.color, thingie.shape, thingie.noise) # purple spiral whrrrrrr ``` --- ### Getters and setters Using getters and setters lets you have methods that behave like properties—these methods will run without being invoked when you attempt to access or update the value. This gives your class a convenient interface for more complicated logic that happens behind the scenes, and it could also be useful for protecting certain "private" values on instances of your class. Python doesn't have any actual "private" attributes—all of the attributes can be accessed or changed from outside the class or instance—so this isn't a security feature. It's just so that other users who interact with your code will understand how it is supposed to be used. --- ### Without getters and setters ```python= class Widget: def __init__(self, color, shape): self.color = color self.shape = shape self.secret_property = None def get_secret_property(self): # log everytime someone accesses the secret property print("oooh someone tried to get the secret property") return self.secret_property def set_secret_property(self, value): # super secret hashing strategy self.secret_property = str(value) + "abcde" * 3 trinket = Widget("orange", "cylinder") print(trinket.get_secret_property()) trinket.set_secret_property("beep") print(trinket.get_secret_property()) ``` --- ### With getters and setters ```python= class Widget: def __init__(self, color, shape, regular_property): self.color = color self.shape = shape self.regular_property = regular_property @property def regular_property(self): # log everytime someone accesses the secret property print("oooh someone tried to get the secret property") return self.secret_property @regular_property.setter def regular_property(self, value): # super secret hashing strategy self.secret_property = str(value) + "abcde" * 3 trinket = Widget("orange", "cylinder", "hops") print(trinket.regular_property) trinket.regular_property = "beep" print(trinket.regular_property) ``` --- ### "Private" properties By convention, if you have private properties you want to protect, use a single underscore to indicate that. We could replace the `secret_property` on our widget with `_property` to follow this convention, e.g.: ```python= @property def regular_property(self): # log everytime someone accesses the secret property print("oooh someone tried to get the secret property") return self._property ``` --- ### Getters Using the `@property` decorator on a method creates a **getter**. The getter will not need to be invoked: the method will run when you attempt to access the value. e.g. ```python= print(trinket.regular_property) ``` causes this method to run. ```python= @property def regular_property(self): # log everytime someone accesses the secret property print("oooh someone tried to get the secret property") return self.secret_property ``` --- ### Setters The decorator used to create a **setter** is `@<getter_method_name>.setter`. ```python= @regular_property.setter def regular_property(self, value): # super secret hashing strategy self.secret_property = str(value) + "abcde" * 3 ``` You do not need to have a setter in order to have a getter, but you do need to have a getter to have a setter. The setter method will run when you try to change the value. ```python= trinket.regular_property = "beep" # equivalent to this line without setter trinket.set_secret_property("beep") ``` --- ### Duck-Typing "If it looks like a duck, and quacks like a duck, it must be a duck" - What does it mean for an object to "look/quack like a duck"? - it has the relevent "magic method" that python uses to implement a given built-in function (like `len()` or `print()`) - What does it mean to "be a duck"? - that means built-in functions will be able to work on classes that you create - Why is this good? - duck-typing means that classes that you define can work as though they are already part of python! --- ### Duck-typing Using duck-typing you can: - have your class work with the addition operator (`+`) - define what equality operator (`==`) means for your class - loop over an instance of your class with `for ... in` - and much more! - duck-typing is powerful! --- ### Duck-typing The default value when you print a user-defined class instance is not typically very helpful... ```python= class Widget: def __init__(self, color, shape): self.color = color self.shape = shape my_new_widget = Widget("blue", "circle") print(my_new_widget) # <__main__.Widget object at 0x1071cebb0> ``` --- ### Duck-typing You can use the `__repr__` method that will let you control what gets printed for your class. ```python= class Widget: def __init__(self, color, shape): self.color = color self.shape = shape def __repr__(self): return f"Widget({self.color}, {self.shape})" my_new_widget = Widget("blue", "circle") print(my_new_widget) # Widget(blue, circle) ``` --- ## Nodes, Trees, and Chess --- ### Nodes and Trees What is a tree? - A tree is a collection of nodes, each node can have a maximum of one parent. - The node at the top of a tree which has no parent is the root node. - Depending on the type of tree, a node can have multiple children, but in a binary tree it can have a maximum of two. - Nodes with no children are called leaf nodes. ``` x / \ x x / / \ x x x / / \ x x x ``` --- ### Depth first search 1. First, fully explore the left side of a tree and each subtree 2. Then move on to the right side. Traveral order: ``` 1 / \ 2 5 / / \ 3 6 9 / / \ 4 7 8 ``` --- ### Breadth first search 1. First, explore all children of a node. 2. Then move on to the next level of the tree. Traveral order: ``` 1 / \ 2 3 / / \ 4 5 6 / / \ 7 8 9 ``` --- ### Nodes and Trees ```javascript= // The start of a node class in JavaScript class Node { constructor(value) { this._value = value; this._parent = null; this._children = []; } } const node = new Node("x"); console.log(node) ``` --- ### Chess What does a knight do? [video description](https://www.chess.com/lessons/how-to-move-the-pieces/the-knight) ![knight_moves](https://i.imgur.com/o8Iejt1.png) --- ### Working on Knight Moves From a given position, the list of possible relative moves could be represented by the following: ```python= possible_moves = [ (-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1), ] ``` --- ### Today's project We will be implementing nodes and trees so that we can represent all of the moves available to a knight as a tree. Each node will have a value that is a set of coordinates on the board, and its children will be the collection of positions a knight at that position could move to. Here are some additional notes for today's project: https://hackmd.io/@jpshafto/S1yh2mmuu
{"metaMigratedAt":"2023-06-16T11:12:27.576Z","metaMigratedFrom":"Content","title":"More Built-ins, Comprehensions, and Classes (Archive)","breaks":true,"contributors":"[{\"id\":\"a6f34c0b-3567-4ed5-ba81-c2299c2d9369\",\"add\":25674,\"del\":7948}]"}
    259 views
   Owned this note