Linguist 278: Programming for Linguists (Stanford Linguistics, Fall 2021)

Class 5: Range, advanced assignments, logical statements, printing, and function definitions

range

  1. The iterator range is an efficient way to create a list of indices, which is very common in the context of 'for'-loops:

    range(5)  ## This is an iterator -- not quite a list.
    
    list(range(5)) ## This is [0, 1, 2, 3, 4].
    
    ## This will print out 2, 3, and 4, each on its own line.
    for i in range(2, 5):
       print(i)
    
    ## This will print out 2, 4, 6, and 8, each on its own line:
    for i in range(2, 10, 2):
       print(i)
    
    ## A common pattern for iterating over the indices of a list `vals`:
    for i in range(len(vals)):
       ## do something with `i`
    
  2. Since range doesn't build its list, it is much faster for very large values. Try these out in the ipython terminal:

    %time x = range(50000000)
    %time x = list(range(50000000))
    

Advanced assignment statements

Python has some very nice conventions for assignment statements:

  1. x += 1 is the same as x = x + 1.

  2. All the math operators support this. For instance, x *= 2, x -= 2 x /= 2.

  3. For strings: s = "abc"; s += "d"

  4. For lists: x = [1,2,3]; x += [4]

  5. Another nice convention is simultaneous assignment: x, y = 1, 2 assigns x the value 1 and y the value 2.

  6. This also works for lists: x, y = [1, 2]

  7. And it even works with nesting: (x, y), z = [['a', 'b'], 'c'] sets x to 'a', y to 'b' and z to 'c'.

  8. With this same convention, one can swap the values of two variables all at once:

    x = 1
    y = 2
    x, y = y, x
    x ## Equals 2 now.
    y ## Equals 1 now.
    

Truth, falsity, and identity

  1. Some noteworthy things about truth and falsity in Python:

    1 == True     ## True!
    2 == False    ## False!
    0 == False    ## True!
    
    1 is True     ## False!
    0 is False    ## False!
    
  2. Truth and falsity on conditionals:

    for x in [None, False, "", 0, [], (), {}]:
       if x:
           print("This code will not execute")
       else:
           print("This code will execute")
    
    for x in [" ", True, [1], (1,), {1: 2}]:
       if x:
           print("This code will execute")
       else:
           print("This code will not execute")
    

Logical operators

  1. You can join boolean-valued statements with and and or, and you can negate them with not. Bracketing will let you control how different pieces of the code associate.

  2. The in operator tests whether the thing on the left is a member of the thing on the right. It works with str, list, tuple, and dict, among others. For dict, it tests the keys.

  3. Some examples inside a function definitions:

    def valid_twitter_username(s):
       return s.startswith("@") and " " not in s and "@" not in s[1: ]
    
    def contains_neither_a_nor_b(s):
       return not ('a' in s or 'b' in s)
    
  4. In connection with the above section Truth, falsity, and identity, I should note that the behavior of not with respect to various things that are informally true and false:

    not None  ## True!
    not 0     ## True!
    not ""    ## True!
    not []    ## True!
    not 1     ## False!
    not " "   ## False!
    not [""]  ## False!
    

Advanced printing

I've taken print() for granted a bit, but it's worth calling out that it is a built-in method, akin to len() and str(). You can feed it lists of objects, and it will do its best to print them out. Here are some illustrative examples that go beyond what we've seen so far:

print("a", "b", "c", sep="_")

print("a", "b", "c", sep="_", end="***")

print("a", "b", "c", sep="_", end="\n\n")

Advanced function signatures

  1. Last week, we reviewed the basics of defining functions: def myfunc(args): where args is the comma-separated list of arguments to the function.

  2. As you've seen already in a few places (see print and sorted above). functions can also have named, optional arguments:

    def exponent(x, pow=2):
       return x**pow
    

    This function exponent can be called as exponent(3), in which case pow is set to 2 according to the default. It can also be set to other values with exponent(x, pow=3) or exponent(x, pow=3).

  3. Whether optional or not, function arguments can be called with their names in Python. For example, exponent(pow=3, x=2) will compute x**pow. This helps if one has a lot of arguments and the order is hard to remember.

  4. All named arguments have to follow all unnamed ones, and unnamed arguments are assumed to be in the same order as they are in the function's definition.

  5. Here's a variation on the first three problems from assignment 1 in which I use an optional argument to give the user control of the denominator value in sd, and, in turn, in zscore:

    import math
    
    def mean(vals):
       return sum(vals) / len(vals)
    
    def sd(vals, sd_corr=1):
       mu = mean(vals)
       if len(vals) < 2:
           return 0.0
       num = 0.0
       for x in vals:
           num += (x - mu)**2
       denom = len(vals) - sd_corr
       return math.sqrt(num / denom)
    
    def zscore(vals, sd_corr=1):
       mu = mean(vals)
       sigma = sd(vals, sd_corr=sd_corr)
       normed = []
       for x in vals:
           normed.append((x - mu) / sigma)
       return normed
    

    Initially, sd_corr=sd_corr seems unintuitive, but it's just a consequence of my having used the same name for this parameter in both sd and zscore.