# Lecture Notes 2/1/2021 ## Simple input and string formatting ```python= # get a string from the user word = input("type some stuff\n") num = 4 # # Four ways of doing string interpolation: # (but you will probably only ever need the last one!) # print using the % operator for interpolation print("%s is cool. %i is a number" % (word, num)) # print using .format operator for interpolation # with keyword arguments print("{stuff} sounds okay and my number is {four}".format(stuff=word, four=num)) # with positional arguments print("{0} sounds okay and my number is {1}".format(word, num)) # print using f"{}" # equivalent to javascript: `${}` print(f"{word} {num}") ``` ## String indexing and methods ### Arithmetic with strings ```python= # adding strings print("cool" + " " + "stuff") # => "cool stuff" # multiplying strings print("c" + "o"*10 + "l") # => "cooooooooool" ``` ### String indexing ```python= # indexing print("Spaghetti"[0]) # => S print("Spaghetti"[4]) # => h print("Spaghetti"[-1]) # => i print("Spaghetti"[-4]) # => e # indexing with ranges print("Spaghetti"[1:4]) # => pag print("Spaghetti"[4:-1]) # => hett print("Spaghetti"[4:4]) # => (empty string) print("Spaghetti"[:4]) # => Spag print("Spaghetti"[:-1]) # => Spaghett print("Spaghetti"[1:]) # => paghetti print("Spaghetti"[-4:]) # => etti # Using invalid indices print("Spaghetti"[15]) # => IndexError: string index out of range print("Spaghetti"[-15]) # => IndexError: string index out of range print("Spaghetti"[:15]) # => Spaghetti print("Spaghetti"[15:]) # => (empty string) print("Spaghetti"[-15:]) # => Spaghetti print("Spaghetti"[:-15]) # => (empty string) print("Spaghetti"[15:20]) # => (empty string) ``` ### Built-in string methods ```python= # .index() # returns index of first index where character is found print("Spaghetti".index("t")) # => 6 print("Spaghetti".index("s")) # => ValueError: substring not found # .count() # returns the number of times the substring is found print("Spaghetti".count("h")) # => 1 print("Spaghetti".count("t")) # => 2 print("Spaghetti".count("s")) # => 0 # .split() # Returns a list (array) of substrings, split on the character passed # defaults to splitting on a space, with no character passed print("Hello World".split()) # => ["Hello", "World"] print("Hello World".split(" ")) # => ["Hello", "World"] print("i-am-a-dog".split("-")) # => ["i", "am", "a", "dog"] # .join() # called on a string, which is used to concatenate a list of strings # this is different from JavaScript where you call it on the array print(" ".join(["Hello", "World"])) # => "Hello World" # ["Hello", "World"].join(" ") JavaScript print("-".join(["i", "am", "a", "dog"])) # => "i-am-a-dog" # .upper() and .lower() # these functions will not mutate the original value a = 'Hello' print(a) # => Hello print(a.lower()) # => hello print(a) # => Hello ``` # Duck typing ## Summary Duck typing - "If it quacks like a duck, it must be a duck" ### What does it mean for an object to "quack like a duck"? That means the object has the relevent "magic method" that python uses to implement a particular built-in function (like `len()` or `print()`). ### What does it mean that it "must be a duck"? That means built-in functions will be able to work on classes that you create. ### Why is this good???? Duck typing makes it so that classes that you define can work as though they are already part of python! - you can make your classes work with the addition operator (`+`) - you can define what equality (`==`) means for your class - you can even allow python to loop over an instance of your class - and more! - ducktyping is **P O W E R F U L** ### When will this be useful? Probably not yet! defining a `__repr__` method to make your object print out a clear representation with the `print()` will be useful soon—once we learn more about classes in a few days. However, duck typing is something that is fundamental to the python language, so it can be interesting to start thinking about what makes python special. ### Code examples In JavaScript (without duck typing!) ```javascript= class Node { constructor(value) { this.value = value; this.next = null; }; getString() { return `Node(${this.value})` } getLength() { if (this.next === null) { return 1; } else { return 1 + this.next.getLength() } } } const myNode = new Node("A") const nodeTwo = new Node ("B") const nodeThree = new Node("B") myNode.next = nodeTwo; nodeTwo.next = nodeThree; // What happens when I console.log a Node? // console.log() will use its default representation // for my object // If I want to print something else, I have to define my // own method console.log(myNode.getString()) // What happens if I try to use .length method? // that method doesn't exist on my class—I would have to // write my own method console.log(myNode.getLength()) ``` In Python (with duck typing!) ```python= class Node: # this is like a JavaScript constructor def __init__(self, value): self.value = value self.next = None # this method makes it possible to define how the print() function # works on members of the Node class def __repr__(self): return f"Node({self.value})" # this method makes it possible to define how the len() function # works on members of the Node class def __len__(self): if self.next is None: return 1 else: return 1 + len(self.next) my_node = Node("A") node_two = Node("B") node_three = Node("C") my_node.next = node_two node_two.next = node_three # What happens when I print a Node? # now it works because it uses the __repr__ method we defined print(my_node) # What happens if I try to use the len function? # now it works because it uses the __len__ method we defined print(len(my_node)) ``` # Truthiness and Equality/Identity ## Falsey values - `0` (from any number type) - `0` - `0.0` - `0j` - Contants -`None` -`False` - Empty sequences - `""` (empty string) - `[]` (empty list) - `set()` (empty set) - `{}` (empty dictionary) - `range(0)` (empty range) - `()` (empty tuple) ## Logical operators We use the keywords `and`, `or`, and `not` in Python instead of `&&`, `||`, and `!` ```python= print(True and False) # => False print(True or False) # => True print(True and not False) # => True ``` Parentheses can group our conditions, just like in JavaScript. ```python= print(False and (True or True)) # => False print((False and True) or True) # => True ``` ### Short circuiting If we can already determine the overall outcome of a conditional, Python won't bother evaluating the rest of the statement—just like in JavaScript. ```python= False and print("not printed") # False and (anything else) is always False, so the print is not evaluated False or print("printed #1") # Cannot determine overall value so we have to evaluate the right side True and print("printed #2") # Cannot determine overall value so we have to evaluate the right side True or print("not printed") # True or (anything else) is always True, so the print is not evaluated ``` ## `While` loops While loops follow a very similar structure to JavaScript ```python= i = 0 while i < 5: print(f'{i+1}. Hello, world.') i += 1 print("You've printed 5 times. Goodbye.") # continue / break keywords # The 'continue' keyword goes to the next iteration of the loop # The 'break' keyword exits out of the loop completely i = 0 while True: print(f"{i+1}. Hello, world.") if i < 4: i += 1 continue print("You've printed 5 times. Goodbye.") break ``` ## Identity vs. equality In JavaScript, `==` is bad, `===` is good. The behavior of `==` regarding different types is counterintuitive, so it should be avoided under almost all circumstances. The `===` operator will compare values when you are dealing with primitive types like numbers, strings, and booleans. However it will compare _identity_ on reference types. (The `==` will do the same—it will just also be weirdly permissive about different types.) ```javascript= // In JavaScript // two different objects with the same contents will not // be evaluated as equivalent because they are not the same // object {} === {} // => false {} == {} // => still false, but also bad practice ``` In Python, `is` and `==` are both useful, just for different things. `is` checks identity, while `==` checks whether the values are equivalent. This is not about typechecking—if you want typechecking, just explicitly check the type. ```python= my_int = 4 my_float = 4.0 # if you want to check equality print(my_int == my_float) # => True # if you want equality and typechecking, use isinstance print(my_int == my_float and isinstance(my_int, float)) # => False ``` When you are checking whether two numbers have the same value, the results of the identity operator would be very counterintuitive, and even unreliable. Usually, when you want to check equivalence, you want to check equality. ### So, when would you use `is`? 1. For checking `is None` ```python= a = [] if a: print("a is truthy") else: print("a is falsey") if a is not None: print("a is not None") else: print("a is None") ``` 2. For checking `is True` / `False` ```python= a = 1 print(a == True) # don't do this print(a is True) ``` 3. For checking whether two objects are the same object ```python= print([] == []) # => True print([] is []) # => False a = [] b = a print(a is b) # => True # mutating b will mutate a as well, because they have the same identity b.append(5) print(a) # => [5] ```