I: As A Calculator

  1. Basic operations (+, -, *, /) work as expected.
  2. Integer division is done with //. Modulo (remainder) is done with %.
In [1]:
4 // 3
Out[1]:
1
In [2]:
4 % 3
Out[2]:
1
In [3]:
10 // 3
Out[3]:
3
In [4]:
10 % 3
Out[4]:
1
  1. Exponentiation
    • Requires no imports (unlike, say, C)
    • Indicated by **
    • WARNING: ^ does something else
In [5]:
2 ** 4
Out[5]:
16

CISV

  • 410 = 1002
  • 210 = 0102
  • 610 = 1102

This is bitwise OR.

In [6]:
4 ^ 2
Out[6]:
6

II: Very Brief Intro to Functions

A function is a structure which takes some arguments and returns some output or performs some action.

Printing to the command line is done using the print function.

In [7]:
print(3)
3

III: Variables and Types

  1. Variable assignment is done with =
  2. Variable names can contain letters, numbers, and underscores, but cannot begin with a number.
  3. The type of a variable indicates what kind of data it stores. The type of some variable can be determined using the type function.
  4. Variables are dynamically typed.
    • The type does not need to be declared in advance.
    • The type can be changed throughout the running of a program.

A: Basic Types

  1. int: An integer (There is also a long in Python, but regular int uses C's long, so you don't usually need Python long).
  2. float: A floating-point number (again, no size, like double, is specified).
  3. bool: A boolean
    • Either True or False (note the capital letters).
    • We'll discuss "truthiness" later
  4. NoneType: indicated using keyword None. Similar to void in other languages.
  5. str: A string of characters
    • Can be indicated using either single or double quotation marks (but The end must be the same as the beginning).
    • Quotations set between a pair of three quotation marks (of either kind) preserve space (line breaks, tabs, etc.) between them. Often used for docstrings.
    • An r before the opening quotation marks means that backslashes are not interpreted within the string. These are called "raw strings."
In [8]:
# Declare an integer:
a = 3
print(a)
3
In [9]:
# Declare a float:
b = 4.0
print(b)
4.0
In [10]:
# Add the integer and float. The integer is automatically cast to a float:
c = a + b
print(c)
print(type(c))  
7.0
<class 'float'>
In [11]:
# Declare some regular strings:
d = 'The quick brown fox'
e = "jumps over the lazy dog."
In [12]:
# Plus sign concatenates strings.
# Strings can be used without being declared.
f = d + ' ' + e
print(f)
The quick brown fox jumps over the lazy dog.
In [13]:
# Create a multi-line string:
g = """This is a
multi-line string."""
print(g)
This is a
multi-line string.
In [14]:
# Create a string with an escape sequence
h = "This string is\n not raw."
print(h)
This string is
 not raw.
In [15]:
# Create a similar string, but make it raw.
i = r"This string is\n raw."
print(i)
This string is\n raw.
In [16]:
# Also, there are complex numbers:
j = complex(3, 4)
print(j)
print(j.conjugate())
print(j.conjugate()*j)
(3+4j)
(3-4j)
(25+0j)

B: Lists and Tuples

  • Ordered sequences of data.
  • Not all elements need to have the same type.
  • Elements are accessed using varname[index]. Indices start at 0.

1: Lists (type list)

Indicated by square brackets: [<>, <>, <>, ...]

In [17]:
# Declare a list:
list1 = [1, 3.14, False, 'dog']
print(list1)
[1, 3.14, False, 'dog']
In [18]:
# Access an element:
print(list1[3])
dog

Lists are mutable, meaning that you can

  • Change the values of elements:
In [19]:
list1[3] = 'cat'
print(list1)
[1, 3.14, False, 'cat']
  • Append an element to the end:
In [20]:
list1.append(None)
print(list1)
[1, 3.14, False, 'cat', None]
  • Insert an element at some index:
In [21]:
list1.insert(1, 'dog')
print(list1)
[1, 'dog', 3.14, False, 'cat', None]
  • Remove an element from a list by index (returning the removed item):
In [22]:
item = list1.pop(4)
print(list1)
print(item)
[1, 'dog', 3.14, False, None]
cat
  • Remove the first element of a specified value (without returning anything):
In [23]:
list1.remove(1)
print(list1)
['dog', 3.14, False, None]

2: Tuples (type tuple)

Indicated by parentheses: (<>, <>, <>, ...)

In [24]:
# Declare a tuple
tup1 = (1, 3.14, False, 'dog')
print(tup1)
(1, 3.14, False, 'dog')
In [25]:
# Access an element:
print(tup1[3])
dog

Tuples are Immutable. They cannot be changed. To change a value, you must create a new tuple.

In [26]:
# Attempt to change an element:
tup1[3] = 'cat'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-35b31470f687> in <module>
      1 # Attempt to change an element:
----> 2 tup1[3] = 'cat'

TypeError: 'tuple' object does not support item assignment

3: Combining Tuples and Lists

Tuples and lists can be converted into each other:

In [27]:
list2 = list(tup1)
tup2 = tuple(list1)
print(list2)
print(tup2)
[1, 3.14, False, 'dog']
('dog', 3.14, False, None)

Lists and tuples can be nested in a variety of ways

In [28]:
list3 = list2 + [tup2]
print(list3)
[1, 3.14, False, 'dog', ('dog', 3.14, False, None)]
In [29]:
tup3 = (tup2, list2)
tup3
Out[29]:
(('dog', 3.14, False, None), [1, 3.14, False, 'dog'])
In [30]:
tup3[1][2] = True
print(tup3)
(('dog', 3.14, False, None), [1, 3.14, True, 'dog'])

C: Dictionaries (type dict)

  • Containers of key - value pairs
  • Equivalent to hash types in other languages
  1. Creating dictionaries directly:

    dict_name = {<key1>: <val1>, 
                 <key2>: <val2>, 
                 ...}
    

    Warning: all keys must be immutable.

In [31]:
dict1 = {'key1': 'val1',
         'key2': 'val2',
         3     : 'val3',
         None  : 'valNone'}
  1. Creating dictionaries from a list of tuples of key-value pairs
    dict_name = dict( [(<key1>, <val1>), (<key2>, <val2>), ...] )
    
In [32]:
dict2 = dict([('key1', 'val1'),
              ('key2', 'val2'),
              (3, 'val3'),
              (None, 'valNone')])
  1. Accessing elements
    dict_name[<key>]
    
In [33]:
print(dict1['key1'])
print(dict2[None])
val1
valNone
  1. Adding elements
    dict_name[<new_key>] = <new_val>
    
In [34]:
dict2[(4, 5)] = 'tuples can be keys'
print(dict2)
{'key1': 'val1', 'key2': 'val2', 3: 'val3', None: 'valNone', (4, 5): 'tuples can be keys'}
  1. Getting all keys as a dict_keys object and turning it into a list:
    keys_as_dict_keys = dict_name.get_keys()
    keys_as_list = list(keys_as_dict_keys)
    
In [35]:
dict2_keys = dict2.keys()
print(dict2_keys)
print(list(dict2_keys))
dict_keys(['key1', 'key2', 3, None, (4, 5)])
['key1', 'key2', 3, None, (4, 5)]
  1. Getting all values, and getting key-value pairs:

    values = dict_name.values()
    items = dict_name.items()
    

    These variables now refer to special containers for the respective kinds of things.

    They can be turned into simple lists using

    list(values)
    list(items)
    

    as before.

In [36]:
values = dict2.values()
print(values)
items = dict2.items()
print(items)
dict_values(['val1', 'val2', 'val3', 'valNone', 'tuples can be keys'])
dict_items([('key1', 'val1'), ('key2', 'val2'), (3, 'val3'), (None, 'valNone'), ((4, 5), 'tuples can be keys')])

Warning: Dictionary elements have no guaranteed order. There is no guarantee that items will be returned in the order in which they are added. However, there is a guarantee that, as long as the dictionary is not changed between calls, successive calls to any or all of keys(), values(), and/or items() will return elements in the same/corresponding orders.


IV: Comparisons and Membership

A: What Is Truth?

For purposes of comparisons and other boolean expressions, the following are considered to be equivalent to False:

  • False
  • None
  • 0
  • 0.0
  • [] (the empty list)
  • "" (an empty string)
  • () (an empty tuple)
  • {} (an empty dictionary)

Everything else is treated as true.

In [37]:
# The false things
things = [ False, None, 0, 0.0, '', [], {}, () ]
for i, t in enumerate(things):
    print(i, ':', t, "is", bool(t))
0 : False is False
1 : None is False
2 : 0 is False
3 : 0.0 is False
4 :  is False
5 : [] is False
6 : {} is False
7 : () is False
In [38]:
# Some True things
things = [ True, -1, 0.5, 'a', [False], {False: False}, (False, False) ]
for i, t in enumerate(things):
    if t:
        print(i, ':', t, "is", bool(t))
0 : True is True
1 : -1 is True
2 : 0.5 is True
3 : a is True
4 : [False] is True
5 : {False: False} is True
6 : (False, False) is True

B: Equality

  • Identical: Do two things point to the same thing in memory? Tested by the keyword is.

  • Equal: Do the two things represent the same thing? Tested using double-equal sign ==.

  • Unequal: As in most languages, it's tested using !=.

  • Not identical: Tested using is not.

In [39]:
print(0 is 0)
print(0 == 0)
True
True
In [40]:
a = 0
b = 0
print(a is b)
print(a == b)
True
True
In [41]:
a = True
b = 1
print(a is b)
print(a == b)
False
True
In [42]:
a = ['a', 'b', 'c']
b = ['a', 'b', 'c']
print(a is b)
print(a == b)
False
True
In [43]:
a = ('a', 'b', 'c')
b = ('a', 'b', 'c')
print(a is b)
print(a == b)
False
True
In [44]:
a = ['a', 'b', 'c', 'd']
b = ['a', 'b', 'c']
print(a is b)
print(a == b)
False
False

Be careful with lists (and other mutables). Variable assignment is done by reference, not by value.

In [45]:
a = ['a', 'b', 'c']
b = a
print(a is b)
print(a == b)
True
True
In [46]:
b[1] = 'd'
print(a is b)
print(a == b)
True
True
In [47]:
print(a)
print(b)
['a', 'd', 'c']
['a', 'd', 'c']

Be careful with floating-point numbers. Rounding errors can cause problems.

In [48]:
a = 1/3
b = 0.1/0.3
print(a)
print(b)
print(a == b)
0.3333333333333333
0.33333333333333337
False

C: Comparison

Greater-than, Less-than, and the like work as you'd expect in comparing items of the same type:

  • For numbers, it's pretty obvious. Though, again, remember the issues with floating-point numbers.
  • For strings, standard rules of alphabetization apply.

When comparing objects of different types, there are rules. Some work and make sense...but you should look them up.

D: Boolean Operations

Unlike in most languages, and, or, and not are done using the English words just given, rather than strange symbols like &&, ||, and !.

Notes

  • or and and are short-circuit operators.
    • For or to be true, only one of the operands needs to be true. If the first operand is true, the second isn't even checked, and the output is True.
    • For and to be true, both operands must be true. If the first is false, the second is not checked, and the output is False
  • not has lower priority than non-boolean operators.
    • Consider not a == b. == is evaluated before not is even considered, so the expression is equivalent to not (a == b).
    • Consider a == not b. The interpreter reads this as a == not before considering b. This will give an error. The intended meaning is a == (not b).
  • For clarity, when there is doubt about order of operations, use parentheses generously.
  • Boolean expressions can be nested.
In [49]:
a = 1
b = 3
c = 3
d = 10
In [50]:
a < b < c < d
Out[50]:
False
In [51]:
a <= b <= c <= d
Out[51]:
True

E: Membership

The in keyword tests whether

  • some given element equals some element in a list or tuple,
  • some given element equals one of the keys of a dictionary, or
  • some given string is a substring of another.
In [52]:
a = ['apple', 'balloon', 'cat', 'dog']
b = {'fruit': 'apple',
     'toy': 'balloon',
     'bad animal': 'cat',
     'good animal': 'dog'}
c = 'The quick brown fox jumps over the lazy dog'
In [53]:
print('good animal' in b)
True
In [54]:
print('dog' in b)
False
In [55]:
print('fox' in c)
True
In [56]:
print('foxes' in c)
False

V: Control Structures

In Python, logical groupings are indicated by indentation levels (rather than curly braces, as in C or Java). By convention, each indentation level is four spaces (tabs are frowned upon).

A: If, Else

For a simple conditional, the syntax is

if <condition>:
    <code>

code will be executed if and only if condition is true. The code may occur on more than one line (which all must be indented by the same amount relative to if).

In [57]:
if 3 <= 4:
    print('I know numbers.')
I know numbers.

To execute some other code if the condition failes, add an else block.

if <condition>:
    <code>
else:
    <other code>
In [58]:
if 3 <= 4:
    print('I know numbers.')
else:
    print('I do not know numbers.')
I know numbers.

More complicated lists of conditions are done using elif blocks.

if <condition1>:
    <code1>
elif <condition2>:
    <code2>
else:
    <else code>
In [59]:
x = 50
if x < 3:
    print('The variable is small.')
elif x < 100:
    print('The variable is not too large.')
else:
    print('The variable is too large.')
The variable is not too large.

B: Loops

Python does not have a regular for loop, but only what in other languages would be called for each loops. The syntax is

for <var> in <iterable>:
    <code>

<iterable> is any sequence-like quantity (a list, tuple, dictionary, or string, though note that order may be unpredictable for a dictionary). For each iteration of the loop, one element of the iterable is available through the variable var.

In [60]:
elements = ['H', 'He', 'Li', 'Be', 'B']
for element in elements:
    print(element)  
H
He
Li
Be
B

If you want to iterate over numbers, or simply do the same thing multiple times, use the built-in function

range(start, stop, step)
  • If only one argument is supplied, it is treated as stop, with start=0 and step=1.
  • If two arguments are given, they are treated as start and stop, with step=1.
  • stop is not included in the range.
In [61]:
for i in range(1, 9, 2):
    print(i)
1
3
5
7

If you want an integer index and successive values in an iterable, use the enumerate function. The basic syntax is

for <index>, <var> in enumerate(<iterable>):
    <code>
In [62]:
for i, element in enumerate(elements):
    print('Element', i+1, 'is', element)
Element 1 is H
Element 2 is He
Element 3 is Li
Element 4 is Be
Element 5 is B

The syntax for a while loop is similar to that in other languages.

while <condition>:
    <code>
In [63]:
i = 1
while i < 10:
    print(i)
    i += 1
1
2
3
4
5
6
7
8
9

C: Functions

Don't repeat yourself. If there's some task you're going to want to perform frequently on different values, put it into a function. Functions are defined using the def keyword:

def <function_name>(<par1>, <par2>, <par3>=<default>, ...):
    <code>
    return <output>
  • Parameters with an =<default> are called optional arguments or keyword arguments. If no value is given when the function is called, <default> is used.
  • Parameters without a default are called positional arguments.
  • When the function is called, positional arguments must be supplied first.
  • As soon as you start supplying arguments in the form <argument name> = <value>, all later arguments must be supplied in that form, and then order no longer matters.
In [64]:
def get_weighted_average(values, power=1):
    total = 0
    num = 0
    for value in values:
        total += value ** power
        num += 1
    return total / num
In [65]:
get_weighted_average([2, 3, 5, 9], 1)
Out[65]:
4.75
In [66]:
get_weighted_average([2, 3, 5, 9])
Out[66]:
4.75
In [67]:
get_weighted_average([2, 3, 5, 9], 2)
Out[67]:
29.75

VI: Formatting Strings

String formatting is done using format strings, which contain literal text and replacement fields. The format of each replacement fields is as follows.

"{" [field_name] ["!" conversion] [":" format_spec] "}"

Note that each field is surrounded by braces. If you want a brace to be interpreted as literal text, you escape it using double braces '{{' or '}}'.

Each of these is optional. We'll return to conversion and format_spec later.

A: Applying the Format and Field Names

There are two common ways of applying format strings.

  1. Create a regular string, containing text and replacement fields, and then call the strings format function.

    Here are some examples.

In [68]:
s = 'The quick brown {animal} jumps over the {adjective} dog.'
s.format(animal='fox', adjective='lazy')
Out[68]:
'The quick brown fox jumps over the lazy dog.'

But field_name is optional, too. Without field names, arguments are passed positionally:

In [69]:
s = 'The quick brown {} jumps over the {} dog.'
s.format('fox', 'lazy')
Out[69]:
'The quick brown fox jumps over the lazy dog.'

field_name can also be a number, in which case it will be interpreted as the position of the argument. We could then reverse the inputs:

In [70]:
s = 'The quick brown {1} jumps over the {0} dog.'
s.format('fox', 'lazy')
Out[70]:
'The quick brown lazy jumps over the fox dog.'
  1. Create the string as a format string, by putting an 'f' in front of the opening quotation mark. In that case, whatever goes in the field_name slot is evaluated as Python code. Note that the field_name is not optional when using f-strings.
In [71]:
s = f'The quick brown {animal} jumps over the {adjective} dog.'
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-71-29959ba67d1a> in <module>
----> 1 s = f'The quick brown {animal} jumps over the {adjective} dog.'

NameError: name 'animal' is not defined
In [71]:
animal = 'fox'
adjective = 'lazy'
s = f'The quick brown {animal} jumps over the {adjective} dog.'
print(s)
The quick brown fox jumps over the lazy dog.

f-strings allow you to do fancy things in the format strings.

In [72]:
s = f'The quick brown {animal} jumped over the {adjective} dog {3+4} times.'
print(s)
The quick brown fox jumped over the lazy dog 7 times.
In [73]:
other_animal = 'cat'
s = f'The quick brown {animal + " and " + other_animal} jumped over the {adjective} dog {3+4} times.'
print(s)
The quick brown fox and cat jumped over the lazy dog 7 times.

CISV Of course, that example was silly. It would have been simpler to do this.

In [74]:
#CISV
s = f'The quick brown {animal} and {other_animal} jumped over the {adjective} dog {3+4} times.'
print(s)
The quick brown fox and cat jumped over the lazy dog 7 times.

B: Conversion

The conversion flag can be omitted, or it can be one of

  • '!s', which calls str() on the value
  • '!r', which calls repr() on the value
  • '!a', which calls ascii() on the value

C: Format Specification

The format_spec can be omitted, in which case defaults are used based on the type of the value supplied. The supported type specs depend on the type of the value.

The full format_spec may have the following parts:

[[fill]align][sign][0][width][grouping][.precision][type]

Since type is the parameter most commonly supplied, here is a list of the most common values (though without any of the other flags, specifying only a type is not usually more useful than leaving format_spec out altogether).

  • If the value is a str, 's' is the only supported type, so it can be omitted.
  • If the value is a int, the following are often used.
    • 'd' : returns the number in base 10 (this is default and can be omitted)
    • 'b' : returns the number in base 2 (binary)
    • 'x' or 'X' : returns the number in base 16 (hex). The case of the letter determines the cases of letters in the value
  • If the value is a float, the following are common.
    • 'f' : fixed-point notation (the normal way to write numbers with digits after a decimal point). The default number of decimal places is 6.
    • 'e' or 'E' : scientific notation, with a default number of decimal places of 6. The case determines the case of the "e" or "E" separating the number from the exponent.
    • 'g' : "general format." Behaves like either 'e' or 'f', depending on the size of the number, and tries to remove unnecessary zeroes from the end. This is the default.
    • '%' : percentage (similar to 'f', but the number is multiplied by 100 and has a percent sign appended.

For the other flags, we'll go one at a time, moving (roughly) right to left, first definining some values to play with.

In [75]:
v1 = 1234.5
v2 = 299792458
v3 = 3.1415826535
print(f'{v1}')
print(f'{v2}')
print(f'{v3}')
1234.5
299792458
3.1415826535
In [76]:
print(f'{v2:x}')
11de784a
  • .precision specifies the number of digits to keep after a decimal point. If the type flag is either 'f' or 'e', the default is 6.
In [77]:
print(f'{v1:.3f}')
print(f'{v2:.3f}')
print(f'{v3:.3f}')
1234.500
299792458.000
3.142
  • grouping specifies the thousands separator. It may be blank (no grouping), ',', or '_'.
In [78]:
print(f'{v1:_.3f}')
print(f'{v2:,.3f}')
print(f'{v3:,.3f}')
1_234.500
299,792,458.000
3.142
  • width determines the minimum amount of space occupied by the value.
In [79]:
print(f'{v1:20_.3f}')
print(f'{v2:20,.3f}')
print(f'{v3:20,.3f}')
           1_234.500
     299,792,458.000
               3.142

Clearly, the numbers are by default right-aligned.

  • align specifies the alignment of the text within the allotted space. The options are
    • '<' : left-aligned (default for most things)
    • '>' : right-aligned (default for numbers)
    • '^' : center-aligned
In [80]:
print(f'{v1:^20_.3f}')
print(f'{v2:20,.3f}')
print(f'{v3:<20,.3f}')
     1_234.500      
     299,792,458.000
3.142               
  • fill determines what character should take up the blank space to satisfy width and align. It can be any character except a brace.
In [81]:
print(f'{v1:)^20.3f}')
print(f'{v2:20.3f}')
print(f'{v3:*<20.3f}')
))))))1234.500))))))
       299792458.000
3.142***************
  • sign can be
    • '+' : every number gets a sign, whether positive or negative
    • '-' : only negative numbers get a sign
    • ' ' : only negative numbers get a sign, but positive numbers get an extra space in front to help them align with negative numbers
In [82]:
print(f'{v1:+20.3f}')
print(f'{v2:-20.3f}')
print(f'{v3: 20.3f}')
           +1234.500
       299792458.000
               3.142
  • If the [0] is present before the width, then the padding is done with zeroes rather than spaces.
In [83]:
print(f'{v1:+020.3f}')
print(f'{v2:-020.3f}')
print(f'{v3: 020.3f}')
+000000000001234.500
0000000299792458.000
 000000000000003.142

VII: Working with Files

A: Loading Data from Files

To open a file, use

with open('<path to file>') as <handle>:
    <code>

The with structure makes sure that resources used to access the file are closed at the end of the block. <code> should be something that pulls data out of the file.

The open function returns a file object. It takes the path to the file (either relative or absolute) as its first argument. It has a second, optional argument to indicate the mode:

  • 'r' : read mode (default).
  • 'w' : write mode (this will overwrite the contents of the file.
  • 'a' : append mode (writing will append to the end of the file.

The main options for pulling data are

  • file.read(): Read the whole file as a single string.
  • file.readlines(): Read the file as a list of strings, one string per line.

Use the second with the example file.

In [84]:
with open('LRC_resonant_data.csv') as f:
    data = f.readlines()

To work mathematically with the data, we need to clean it up.

  1. Remove whitespace from the beginning and end of each line using the str.strip() method.
  2. Separate the two columns of data at the comma using the str.split(<delimiter>) method.
  3. Separate the column labels from the numerical data.
  4. Transpose the matrix, so that we can access the columns more easily.
In [85]:
for i, row in enumerate(data):
    data[i] = row.strip().split(',')

A more elegant and computationally efficient method uses a list comprehension:

In [ ]:
data = [row.strip().split(',') for row in data]

The heading can be separated using slicing.

In [86]:
heading = data[0]
data = data[1:]

We could get easy access to the columns using a for loop, as follows.

In [87]:
# CISV
columns = [ [] for _ in range(len(data[0])) ]
for row in data:
    for i, col in enumerate(row):
        columns[i].append(float(col))

Now the magic way.

In [88]:
# CISV
columns = [list(col) for col in zip(*data)]

Aside: Why the magic works

Demonstrate what zip does
In [89]:
# Create some smaller arrays and zip them up
a1 = [1, 2, 3, 4, 5]
a2 = ['a', 'b', 'c', 'd', 'e']
a3 = [0.1, 0.2, 0.3, 0.4, 0.5]

z = zip(a1, a2, a3)
In [90]:
list(z)
Out[90]:
[(1, 'a', 0.1), (2, 'b', 0.2), (3, 'c', 0.3), (4, 'd', 0.4), (5, 'e', 0.5)]
Demonstrate what the star does
In [91]:
# Create a simple function

def join(a, b, c, d):
    """Combine the strings, separating them with spaces, and add a
    period.
    """
    return f'{a} {b} {c} {d}.'
In [92]:
# Create some arguments to play with in a list.

args = ["Do not", "drink", "battery", "acid"]
In [93]:
# Supplying the list does not work... `join` expects four arguments

join(args)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-93-de5ec344f270> in <module>
      1 # Supplying the list does not work... `join` expects four arguments
      2 
----> 3 join(args)

TypeError: join() missing 3 required positional arguments: 'b', 'c', and 'd'
In [94]:
# CISV
# We could supply them by index

join(args[0], args[1], args[2], args[3])
Out[94]:
'Do not drink battery acid.'
In [95]:
# CISV
# But that's tedious, and it doesn't generalize to numbers other
# than four (for this case)
# The star **unpacks** the arguments

join(*args)
Out[95]:
'Do not drink battery acid.'

B: Manipulate the Data as Needed

C: Write the Data to a File

  1. Transpose back to rows.
In [96]:
rows = [list(row) for row in zip(*columns)]
  1. Open a file.
  2. Turn each row into a string and write it to the output file.
In [97]:
with open('LRC_resonant_data_out.csv', 'w') as outfile:
    outfile.write(f'{heading[0]},{heading[1]}\n')
    for f, V in rows:
        outfile.write(f'{f},{V}\n')
In [ ]: