---
# System prepended metadata

title: Lecture Notes 2/1/2021

---

# 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]
```