Hola, hope everyone had a good holiday weekend. Let’s discuss how to copy lists.

Copying lists comes up fairly frequently. The most inuitive way to copy a list is simply to assign (=) an existing list to our new list:


ToDo_Monday = ['Grocery Store', 'Buy Gas', 'Pick up laundry']
ToDo_Tuesday = ToDo_Monday

print('ToDo_Monday: ',ToDo_Monday)
print('ToDo_Tuesday: ',ToDo_Tuesday) 

We’ve all had those days. This does what we might expect; both lists are the same:

ToDo_Monday:  ['Grocery Store', 'Buy Gas', 'Pick up laundry']
ToDo_Tuesday:  ['Grocery Store', 'Buy Gas', 'Pick up laundry']

But here’s the wrinkle - we’ve only pointed “ToDo_Tuesday” to the list “ToDo_Monday.” I’ll prove it to you. If I modify ToDo_Tuesday after the assignment, then print both lists:

ToDo_Monday = ['Grocery Store', 'Buy Gas', 'Pick up laundry']
ToDo_Tuesday = ToDo_Monday

print('ToDo_Monday: ',ToDo_Monday)
print('ToDo_Tuesday: ',ToDo_Tuesday)

ToDo_Monday.append('Eat noodles')
print('ToDo_Tuesday: ',ToDo_Tuesday)

We get:

ToDo_Monday:  ['Grocery Store', 'Buy Gas', 'Pick up laundry']
ToDo_Tuesday:  ['Grocery Store', 'Buy Gas', 'Pick up laundry']
ToDo_Tuesday:  ['Grocery Store', 'Buy Gas', 'Pick up laundry', 'Eat noodles']

Sadness. So, all we’ve done is point ToDo_Tuesday to reference the same data as ToDo_Monday. We can’t modify them independently. How else might we copy our lists?

By Slicing

The first method we’ll use is by Slicing. Remember the : operator? It allows us to pull only some part of a given list, or slice.


Gives us elements indexed 1 and 2 (the 3 is not inclusive):

```['Buy Gas', 'Pick up laundry']```

Another use (conveniently) for slicing is to create a copy. If we don't enter indicies, we get a copy of the entire original list:

```python:
ToDo_Monday = ['Grocery Store', 'Buy Gas', 'Pick up laundry']
ToDo_Wednesday = ToDo_Monday[:]

ToDo_Monday.append('Eat noodles')

print("ToDo_Monday: ", ToDo_Monday)
print("ToDo_Wednesday: ", ToDo_Wednesday)
ToDo_Monday:  ['Grocery Store', 'Buy Gas', 'Pick up laundry', 'Eat noodles']
ToDo_Wednesday:  ['Grocery Store', 'Buy Gas', 'Pick up laundry']

So now, we’ve got an actual copy - the append operation to ToDo_Monday does not affect ToDo_Wednesday, so they’re not referencing the same list. However, there’s a wrinkle here too. Any lists inside our copied list are only referenced, so they suffer the same issue as before:

ToDo_Monday = ['Grocery Store', 'Buy Gas', ['Call Jim']]
ToDo_Thursday = ToDo_Monday[:]

ToDo_Monday[2].append('Call Doc')

print("ToDo_Monday: ", ToDo_Monday)
print("ToDo_Thursday: ", ToDo_Thursday)
ToDo_Monday:  ['Grocery Store', 'Buy Gas', ['Call Jim', 'Call Doc']]
ToDo_Thursday:  ['Grocery Store', 'Buy Gas', ['Call Jim', 'Call Doc']]

This is referred to as a Shallow Copy, as nested data structures are only referenced.

DeepCopy

What we need is a Deep Copy. No references, no nada - give me a fully functional copy of my original list:

import copy

ToDo_Monday = ['Grocery Store', 'Buy Gas', ['Call Jim']]
ToDo_Friday = copy.deepcopy(ToDo_Monday)

ToDo_Monday[2].append('Call Doc')

print("ToDo_Monday: ", ToDo_Monday)
print("ToDo_Friday: ", ToDo_Friday)



And finally, voila, our fully seperate copy,

ToDo_Monday:  ['Grocery Store', 'Buy Gas', ['Call Jim', 'Call Doc']]
ToDo_Friday:  ['Grocery Store', 'Buy Gas', ['Call Jim']]

Lists

Python’s basic collection data structure is the List. Much like an Array, a list is an ordered sequence of elements. Lists are mutable (they can be changed), and maybe most importantly, can store data of different types. For example here’s a list named “box”:

box = [5, True, [1,4], 42.5]

Our list includes an int, Bool, another list, and a float. This flexibility means we can add items dynamically as our program progresses.

In addition, we can reference a specific piece of data at a given index, or iterate through a list. A given operation can act on each element in order; let’s define a list of numbers, and figure out which ones are even and which are odd. Using the mod operator (% in Python) will give us the remainder left after dividing by a given number. By choosing mod 2, we’ll know which are odd by those that have non-zero values afterwards, like so:

numbers = [3 6 5 12 42]
for item in numbers:
  print(item%2)
  

Which tells us only 3 and 5 are even:

1
0
1
0
0
[Finished in 0.0s]

Self. What is it, why do I care?

Happy Saturday! Let’s talk about self. No, not me, your self - nevermind.

Why is the self argument required in a method, and what good does it do me? First, some context. My program has a menu class for users to make a selection:

print(“1) Thing 1” + “\n 2) Thing 2” + “\n 3) Thing 3”) choice = input(“Select one:”) build(choice)

The user enters a number, and the module then calls this class’ method build(). When build() is called, however, Python returns:

build() takes 1 positional argument but 2 were given

What gives? We only included one argument.

Well, kind of. We included only one explicit argument. That’s not all that’s going on here. Methods by definition include an implicit self argument when called, in addition to what’s explicitly passed. build() always passes the self argument at minimum. Behind the scenes, build(choice) really looks like (and remember, “object” is the instance of our class here)

Class.build(object, choice)

Two arguements! This “object” is usually refered to as self. So why do we care? This is great: Self represents a class instance from inside itself. That means we have convienent access to an instance’s attributes and methods from inside the build() method. Anything my Object has as an attribute, build() can use with the syntax self.attribute, or methods with self.method().

Scope Basics

Tripped on this today. Let’s talk about scope! Python organizes scope as LEGB: Local, Enclosing, Global, Built-in.

On to the example. This code doesn’t work:


class Structure:

structureName = None
structureDescription = None

def info(self):
    print("########   " + structureName + "  #########")
    print(structureDescription)

greatHall = Structure() greatHall.info()


The scope of info() includes structureName in the method, but does NOT include structureName at the enclosing level. So, it’s out of scope. This is fixed by indicating where structureName actually is:


def info(self):
    print("########   " + self.structureName + "  #########")
    print(self.structureDescription)

I initially thought this error had to do with a subclass, but including the self arguement in Structure.info() makes this work as well:


class Town_hall(Structure):

structureName = "Town Hall"
structureDescription = "Spawn workers from here!"

greatHall = Town_hall() greatHall.info()

Hello World!

First! Welcome, everyone.

Don’t get stuck where I did! Read along as I share tips, tricks, and core concepts about using Python to solve real-world problems.