CS193Q - Day 2

See Python Guide for reference Python Guide

Today: more string functions, foreach loop, lists, for/i/range loops, Doctests in PyCharm FTW

Today: Install PythonCharm

Install PyCharm on your computer. There is supposed to be a version that is free. https://www.jetbrains.com/pycharm/download/ - use it later today. You could use a different Python IDE if you prefer. I want to use PyCharm to show off how to use Doctests as part of a productive workflow.

More String functions

Last time - we did the first half of these, here's the full list

Python Guide: Python Strings

See String chapter in the guide

>>> s
'Python'
>>> 
>>> 'th' in s     # "in" to check
True
>>> 'x' in s
False
>>> 'x' not in s  # "not in" variant
True
>>> 
>>> s.find('y')
1
>>> 
>>> s.find('yt')
1
>>> 
>>> 'yt' in s
True
>>> 
>>> s.find('y', 0)
1
>>> s.find('y', 3)
-1
>>> 
>>> 
>>> s.startswith('Py')
True
>>> 
>>> 
>>> s.endswith('Py')
False
>>> 
>>> 
>>> s.lower()
'python'
>>> 
>>> s
'Python'
>>> 
>>> 
>>> s = 'Python!'
>>> 
>>> s.lower()
'python!'
>>> 
>>> s.upper()
'PYTHON!'
>>> 
>>> 'x'.isupper()
False
>>> 
>>> 'x'.islower()
True
>>> 
>>> 
>>> '9'.isdigit()
True
>>> 
>>> 
>>> 'x'.isalpha()
True
>>> 
>>> '!'.isalpha()
False
>>> 
>>> '   key data here    \n'.strip()
'key data here'
>>> 
>>> 
>>> 'this is it'.replace('is', 'is not')
'this not is not it'
>>> 
>>> 
>>> 'this is it'.replace('is', 'xxx')
'thxxx xxx it'
>>> 
>>> # old way to str / int: 
>>> 'score:' + str(45)
'score:45'
>>>
>>> # new - format strings:
>>> name = 'Alice'
>>> 
>>> f'this is {name}'
'this is Alice'
>>> 
>>> x = 12
>>> f'value: {x}'
'value: 12'
>>> 
>>> f'value: {x * 10}'
'value: 120'
>>>
>>> x = 2/3
>>> f'value: {x}'
'value: 0.6666666666666666'
>>> f'value: {x:.4}'
'value: 0.6667'
>>> 

Work With Immutable

The pattern for working with immutable strings:

x = change(x)

>>> s = 'HELLO there'
>>> s.lower()
'hello there'
>>> s
'HELLO there'
>>> s = s.lower()
>>> s
'hello there'

Example: Edit First Char

Say we have a string, and we want to edit it by changing its first char to uppercase, and the rest of it to lowercase. We bump into the issue of immutability. We assemble a new string to hold the result. Here I do it putting the partial results into variables.

>>> s = 'pYTHon'
>>> 
>>> first = s[0]
>>> rest = s[1:]  # slice!
>>> 
>>> first
'p'
>>> rest
'YTHon'
>>>
>>> # Compute and assemble the string I want
>>> first.upper() + rest.lower()
'Python'

String Foreach Loop

The for-loop can iterate over the elements within any "iterable" collection. For these purposes, the string is an iterable collection of chars.

The simple for loop takes a var, and sets it to point to each element from the collection, one per iteration. The easiest way to look at each element. With strings, each element is a single char.

Syntax: for VAR in STR:

On each iteration of loop, var points to next char, 'P', 'y', ...

alt: for-ch-in-s ch points to each char in turn

Here just typing it in the interpreter for a demo. Could use any var name, does not need to be "ch".

>>> s = 'Python'
>>> for ch in s:
...     print(ch)
... 
P
y
t
h
o
n

Common pattern, += to build up a result looping over string...

>>> result = ''
>>> for ch in s:
...     result += ch + ch
... 
>>> # what is result now?
>>> 
>>>
>>>

Answer: 'PPyytthhoonn'


Python List

Python Guide: Python Lists

>>> lst = []
>>> len(lst)
0
>>> 
>>> lst.append('abc')
>>> 
>>> lst
['abc']
>>> 
>>> lst.append('xyz')
>>> 
>>> lst.append('cat')
>>> 
>>> lst
['abc', 'xyz', 'cat']
>>> 
>>> len(lst)
3
>>> 
>>> 'cat' in lst
True
>>> 
>>> lst
['abc', 'xyz', 'cat']
>>> 
>>> 
>>> lst2 = ['dot', 'bird']
>>> 
>>> lst.append(lst2)
>>> lst
['abc', 'xyz', 'cat', ['dot', 'bird']]
>>> 
>>> 
>>> lst = ['abc', 'xyz', 'cat']
>>> 
>>> # + between lists - like string
>>> lst + ['a', 'b']
['abc', 'xyz', 'cat', 'a', 'b']
>>>
>>> lst
['abc', 'xyz', 'cat']
>>> 
>>> 
>>>
>>> lst2
['dot', 'bird']
>>> 
>>> lst + lst2
['abc', 'xyz', 'cat', 'dot', 'bird']
>>> 
>>> 
>>> lst
['abc', 'xyz', 'cat']
>>> 
>>> lst.extend(lst2)
>>> 
>>> lst
['abc', 'xyz', 'cat', 'dot', 'bird']
>>> 
>>> 

Python No Copy

The = never makes a copy, just shallow pointers. There is a .copy() function - very rare to use.

In C/C++ you make copies all the time, often I think because there is no GC. Need copies so they can be deleted ad separate times. It's natural for a Python program works ok with just a bunch of pointers and just 1 copy of each structure. So the .copy() function shown below is rarely used.

>>> a = [1, 2, 3]
>>> b = a
>>> 
>>> b.append(13)
>>> b
[1, 2, 3, 13]
>>> a
[1, 2, 3, 13]
>>> 
>>> # can make explicit copy - rare
>>> c = a.copy()
>>> c.append(42)
>>> c
[1, 2, 3, 13, 42]
>>> a
[1, 2, 3, 13]

Foreach Loop 1

for s in ['a', 'b', 'c', 'd']:
   print(x)

alt: loop over strings in list

Range Functions

range(6)  -> [0, 1, 2, 3, 4, 5]  # UBNI
range(2)  -> [0, 1]
range(1)  -> [0]
range(0)  -> []                
range(n)  -> [0, 1, 2 .... n-1]
# lightweight - not creating all numbers
>>> range(10)
range(0, 10)
>>>

# In interpreter, wrap in list() to realize all numbers.

>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list(reversed(range(10)))
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>>
>>> list(range(5, 10))  # 2-param form
[5, 6, 7, 8, 9]
>>>
>>> list(range(1, 20, 2))  # 3 param
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
>>>

Foreach Loop 2

Say we have a string or list length 10. So range(10) will generate its index numbers.

alt: for var in range - var points to 0, 1, 2 .. from range

So we can use for/i/range to generate the index numbers for a list, and then use [i] in the loop to access each element.

>>> lst = ['a', 'b', 'c', 'd']
>>> for i in range(len(lst)):
...   print(i, lst[i])
... 
0 a
1 b
2 c
3 d
>>>

C Translation

# Common C loop
C: for (int i = 0; i < n; i++)

# range(n) is just a canned version of above
Python: for i in range(n):

for/s/lst vs. for/i/range

Two loop forms, looping over, say, a list of strings. Which to use? Basically, for s in lst is easier, so prefer that one. BUT it does not give you index numbers. So if you need index numbers, then use for i in range(len(lst)).

Doctests In PyCharm

A Python IDE has many language related features which really can save you time.

Today: Run PyCharm. Use its "Open..." command to open either (a) the whole code2 folder, or (b) the exercise2.py file within that folder. Either will work, but opening the folder gives you access to all the .py files in that folder, which is typically what you want.

At the bottom right of the window, it should say "Python 3.13" (or whatever your installed Python is). If no, click there to select your Python. You may need to click Add New Interpreter > Add Local Interpreter > System Interpreter > (select the one you want to use). The differences between different 3.11+ versions are small, but you might as well run something recent.

Doctest Demo

If Python is installed in PyCharm, Doctests will appear in a different color. Right-click the Doctest in a function to run just that test. See the results below. Super handy!

Right click in a space between functions ... run all the Doctests in the file.

Doctest Productivity Workflow

Working on some helper function. Write a few Doctests next to the code itself. Can write these before writing any code. Now work on the code .. check the tests as you go. When the function works, it's tested as well, and you can move on to the next thing with confidence.

Lesson of modern coding: tests do not need to be exotic or difficult to catch most bugs. Have 1 or 2 "basic" tests that are obvious cases. then add a couple "edge" cases (e.g. empty string) at the boundaries. Small, obvious tests catch many, many bugs.