In [ ]:
arbitrary_string = "Mike's Hello World"

In [ ]:
len(arbitrary_string)  # You can type len() around anything that is a squence of things (strings, lists, etc.)

In [ ]:
20  # Typing a number itself with just print it back at you, same as a string.

In [ ]:
type(20)  # In case you're wondering, the type of a real number is int.

In [ ]:
type(20.1)  # But the type of a number with a decimal is float.

In [ ]:
a = 0  # When manipulating numeric data-types, they get "copied" as wholly seperate (tiny) objects in memory.
b = a
a = 1
print('b is still %s. That means b must have been a COPY of a.' % b)

In [ ]:
type(a), type(b)

In [ ]:
a = 0.5  # The rules about copying values in situations like this is true for either int's or floats.
b = a
a = 1.5
print('b is still %s. That means b must have been a COPY of a.' % b)

In [ ]:
a = ["I'm", 'missing', 'my', 'pop']  # Attempts to "copy" lists actually make references to original list in memory.
b = a
a.pop()
print("%s pop from the list, so b must reference the same in-memory list as a. It's NOT A COPY." % b)

In [ ]:
type(a), type(b)  # It may not be immediately obvious, but a and be are references to the same list in memory.

In [ ]:
a = ["I'm", 'not', 'missing', 'my', 'pop']  # Use the .copy() method of lists, or the slice api to copy.
b = a.copy()
c = a[:]
a.pop()
print("%s so the b-list must have been an in-memory copy (duplicate) of the original list." % b)
print("%s so the c-list is likewise an in-memory copy (duplicate) of the original list." % c)
print("%s mind beause under Python, I can finally understand passing values by refrence vs. value." % a)

In [ ]:
a = ['three', 'two', 'one']  # Attempts to "copy" lists actually make references to original list in memory.
b = a

In [ ]:
a.pop(0)

In [ ]:
b.pop(0)

In [ ]:
a.pop(0)

In [ ]:
b.pop(0)  # (error intentional)

In [ ]:
try:
    b.pop(0)
except:
    print('BOOM')  # You can always make Python scripts keep running by trapping an error.

In [ ]:
try:
    b.pop(0)
except Exception as e:
    print(e)  # You can display the Exception error message.

In [ ]:
try:
    b.pop(0)
except Exception as e:
    print(type(e).__name__)  # Figuring out the name of the current Exception for later trapping is a wart on Python.

In [ ]:
try:
    b.pop(0)
except IndexError:
    print("Ah ha, I caught that specific error!")  # But once you do figure it out, trapping errors can be very explicit.

In [ ]:
try:
    b.add(1)
except IndexError:
    print("Ah ha, I caught that specific error!")
except Exception as e:
    catch_me = type(e).__name__
    print("You now have to capture: %s" % catch_me)  # See each untrapped exception name as it first occurs.

In [ ]:
print(e)  # However, that "e" error object doesn't get outside its except-scope.

In [ ]:
if False:
    new_var = 1
else:
    new_var = 2  # In this case (unlike in a "try"), the variable new_var is ALWAYS defined. Scope can be funny.
print(new_var)

In [ ]:
starting_value = 1
if False:
    starting_value = 2  # For good readability, declare variables outside if-statements, overriding obvious defaults.
print(starting_value)