Python Tricks

We've covered a lot in Python. Today we wrap up most of the core content with some fun Python shorthand and 'tricks'. These are conveniences that are available to the experienced programmer, and they help you program faster when appropriately used.

Default Function Parameters

Functions have parameters, as you know, but did you know you can define them so that their arguments have default values? We've shown this in small examples throughout the semester, but let's more formally discuss. These default values allow the caller of your function to not include all the arguments if they don't want to.

Here's a basic function we're used to seeing:

def get_positives(N):
    """ 
    Get a list of positive ints from the user.
    Keep asking if they enter negative ints.
    """
    nums = []
    for i in range(N):
        num = -1
        while num < 0:
            num = int(input('Enter an int: '))
        nums.append(num)
    return nums

if __name__ == '__main__':
    nums = get_positives(5)
    print(nums)

This requires the caller to give a number N of how many ints they want. But we could write this so the caller doesn't have to do that!

def get_positives(N=10):
    """ 
    Get a list of positive ints from the user.
    Keep asking if they enter negative ints.
    """
    nums = []
    for i in range(N):
        num = -1
        while num < 0:
            num = int(input('Enter an int: '))
        nums.append(num)
    return nums

if __name__ == '__main__':
    nums = get_positives()
    print(nums)

Look at where we call get_positives() now with no arguments. Neat! This gives the caller some flexibility by defining default values when they leave it unspecified. They can still do get_positives(5) if they just want a list of 5.

def get_positives(N=10, prompt='Enter an int: '):
    """ 
    Get a list of positive ints from the user.
    Keep asking if they enter negative ints.
    """
    nums = []
    for i in range(N):
        num = -1
        while num < 0:
            num = int(input(prompt))
        nums.append(num)
    return nums

if __name__ == '__main__':
    nums = get_positives()   # gets 10
    nums = get_positives(4)  # gets 4
    nums = get_positives(4, 'Give me an int: ')      # gets 4 with a custom prompt
    nums = get_positives(prompt='Give me an int: ')  # gets 10 of them!
    print(nums)

The bottom here shows 4 different ways of calling it. The last one is most interesting. If you have default values in a function, then the caller can specify the exact argument they want to give a value, while skipping others before it.

You can dive deeper into this by reading more about it here.

Setting Multiple Variables

Ok this isn't a trick because we've shown you this too. But it's so useful that we want to go over it again because not enough of you are actually using it when you could/should.

You can set multiple variables at once if the right-hand side of the assignment operator matches the number of variables on the left. Here are a few toy examples:

a, b, c = [ 'hi', 'hey', 'hiya' ]
name,alpha,co = "chambers 203984 23".split()
average,mean = compute_stats(mydata)   # function returns two values as a tuple or list!

In each of these, you're assignment more than one variable on the left. These can be variable declarations (your first use of them) or over-riding values of variables that already exist. If you don't have the same number on both sides then you'll see an error:

a,b,c = [3, 4, 5, 6]
Traceback (most recent call last):
  File "", line 1, in 
ValueError: too many values to unpack (expected 3)

One of the most useful places to do this is with split(). If you know for sure what's in your string, then you know how many items the split call will return, so you can immediately set custom variables to those values.

Sorting with Functions

Sorting is an important algorithm you'll use over and over again. The sorted() function has been our tool for the most part, doing what you'd expect:

>>> nums = [4, 6, 7, -3, 4, 1, -8, 9, 3, -2, 0]
>>> sorted(nums)
[-8, -3, -2, 0, 1, 3, 4, 4, 6, 7, 9]

However, what if we want to sort these numbers based on their absolute values? We're a little stuck here. We need some way of telling Python not to sort them by their numeric comparison (a < b) but rather their comparison after converting each element (abs(a) < abs(b)). Python lets you apply any arbitrary function to each element before it does its comparison. You just give the function an optional argument:

>>> nums = [4, 6, 7, -3, 4, 1, -8, 9, 3, -2, 0]
>>> sorted(nums)
[-8, -3, -2, 0, 1, 3, 4, 4, 6, 7, 9]
>>> sorted(nums, key=abs)
[0, 1, -2, -3, 3, 4, 4, 6, 7, -8, 9]

Look what we did! We combined our first trick with the second. The key argument is a named argument that expects a function as its value. If you don't give one, its default is to use the < comparator.

You can do the same with strings. Let's sort words by their lowercased values even though the words aren't lowercased in the list. To do this, we need to write a lowercase function that takes a string and returns the lowercased version:

def mylower(s):
    return s.lower()

words = [ 'hi', 'Bye', 'Happy', 'hello', 'OK', 'Apple', 'zoo' ]
print( sorted(words) )
print( sorted(words, key=mylower) )

And its output...

['Apple', 'Bye', 'Happy', 'OK', 'hello', 'hi', 'zoo']
['Apple', 'Bye', 'Happy', 'hello', 'hi', 'OK', 'zoo']

If you understand the above, then you see we're just making our own function that accepts one argument, and returns one value. This is a simple function, and in these one-off cases where you just need a quick one-use function, you don't actually have to define it with a name. You can use what's called a lambda function:

words = [ 'hi', 'Bye', 'Happy', 'hello', 'OK', 'Apple', 'zoo' ]
neworder = sorted(words, key=lambda s: s.lower())
print(neworder)

Quick List Creation

Create a list with N instances of the same value:

>>> [3]*20
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
>>> ['hi']*10
['hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi', 'hi']

Create a list of ascending integers (old way):

nums = []
for i in range(10):      
    nums.append(i)
print(nums)

Instead you can convert the range() sequence into a list:

nums = list(range(10))
print(nums)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Python has lots of objects (custom classes from modules!) that are sequences, and you can convert them to proper lists this way. Keep that in mind!

Zip

You'll often find yourself with two sequences of values in two variables, and you want to combine them into one sequence of tuples. You pair up each item at their corresponding indices. The function zip does this for you!

words = [ 'hi', 'Bye', 'Happy', 'hello', 'OK', 'Apple', 'zoo' ]
nums = [0, 1, 2, 3, 4, 5, 6]      

# [(0, 'hi'), (1, 'Bye'), (2, 'Happy'), (3, 'hello'), (4, 'OK'), (5, 'Apple'), (6, 'zoo')]
tuples = zip(nums,words)

This lets you combine related values, and once in a list like this, you can easily sort them!

List Comprehension

List comprehension is a neat and powerful mechanism to filter lists into other lists, as well as perform operations on their values easily. What I just showed you with list creation is the basic starting block, but you can get much more fancy with it. It saves you time by avoiding for loops and .append() calls. Below are a few examples.

This page gives a decent overview for those who want to go deeper into it.

words = [ 'hi', 'Bye', 'Happy', 'hello', 'OK', 'Apple', 'zoo' ]
gonavy = [ 'gonavy-'+x for x in words ]
# ['gonavy-hi', 'gonavy-Bye', 'gonavy-Happy', 'gonavy-hello', 'gonavy-OK', 'gonavy-Apple', 'gonavy-zoo']

      
nums = [0, 1, 2, 3, 4, 5, 6]
squares = [i*i for i in range(10)]
# [0, 1, 4, 9, 16, 25, 36]

      
nums = [0, 1, 2, 3, 4, 5, 6]
evens = [i for i in nums if i%2 == 0]
print(evens)
# [0, 2, 4, 6]


nums    = [0, 1, 2, 3, 4, 5, 6]
factors = [2, 2, 3, 3, 4, 4, 5]
prods   = [ x*y for x,y in zip(nums,factors) ]
# [0, 2, 6, 9, 16, 20, 30]
  

You can even do a dictionary version of these! Take two lists and convert them into a key/value dictionary!

firsts = ['Sara', 'Amy', 'Will', 'John']
lasts  = ['Herron', 'Henderson', 'Smalls', 'Drake']
d      = { k:v for k,v in zip(firsts,lasts) }
# {'Sara': 'Herron', 'Amy': 'Henderson', 'Will': 'Smalls', 'John': 'Drake'}