Python Basics

This page demonstrates some basic Python concepts and essentials. See the Python Tutorial and Reference for more exhaustive resources.

This page provides a brief introduction to:

  • Python syntax
  • Variables
  • Lists and Dicts
  • For loops and iterators
  • Functions
  • Classes
  • Importing modules
  • Writing and reading files with Pickling.

If you are running Linux or OSX, you probably already have Python installed. To run it, simply open a terminal and type python. (Many users prefer to use Jupyter instead. That should work with all of our examples without requiring any modifications.) On Windows, you will probably have to install Python yourself. You could get it directly from python.org or install versions – e.g. Anaconda , Enthought Canopy , Python(x,y) , etc – that package Python with many additional libraries.

Note

If you are not using a distribution (e.g. Anaconda , Enthought , etc) that packages Python with many additional modules, you will need to install at least numpy and matplotlib to run some of the examples in this tutorial series.

On Ubuntu and Debian, you can get these modules via:

sudo apt install python-numpy python-matplotlib

On all platforms, you can use the pip tool to automatically download and install new modules.

The following command simply prints “Hello”. Run it, and then re-evaluate with a different string.

print("Hello")

Variables: Strings, numbers, and dynamic type casting

Variables are easily assigned:

my_name = "Tom"
my_age = 45

Let’s work with these variables.

print(my_name)
print(my_age)

Strings can be combined with the + operator.

greeting = "Hello, " + my_name
print(greeting)

Let’s move on to numbers.

print(my_age)

If you try using the + operator on my_name and my_age:

print(my_name + my_age)

You will get a TypeError. What is wrong?

my_name is a string and my_age is a number. Adding in this context does not make any sense.

We can determine an object’s type with the type() function.

print(type(my_name))
print(type(my_age))

The function isinstance() is also useful.

print(isinstance(my_name, str))

Python also has a special object called None. This is one way you can specify whether or not an object is valid. After evaluating the following script block, set my_valid_var to a value and rerun the four lines beginning with the if statement. The first time, it will complain that the variable is None; the second time it will print its value.

my_valid_var = None
if my_valid_var is not None:
    print(my_valid_var)
else:
    print("The variable is None!")

Warning

In older versions of Python (prior to 3.0), the / operator when used on integers performed integer division; i.e. 3/2 returned 1, but 3/2.0 returned 1.5. Beginning with Python 3.0, the / operator returns a float if integers do not divide evenly; i.e. 3/2 returns 1.5. Integer division is still available using the // operator, i.e. 3 // 2 evaluates to 1.

Lists

Lists are comma-separated values surrounded by square brackets:

my_list = [1, 3, 5, 8, 13]
print(my_list)

Lists are zero-indexed. That is, the first element is 0.

print(my_list[0])

You may often find yourself wanting to know how many items are in a list.

print(len(my_list))

Python interprets negative indices as counting backwards from the end of the list. That is, the -1 index refers to the last item, the -2 index refers to the second-to-last item, etc.

print(my_list)
print(my_list[-1])

“Slicing” is extracting particular sub-elements from the list in a particular range. However, notice that the right-side is excluded, and the left is included.

print(my_list)
print(my_list[2:4])  # Includes the range from index 2 to 3
print(my_list[2:-1]) # Includes the range from index 2 to the element before -1
print(my_list[:2])   # Includes everything before index 2
print(my_list[2:])   # Includes everything from index 2

To make a variable equal to a copy of a list, set it equal to list(the_old_list). For example:

list_a = [1, 3, 5, 8, 13]
list_b = list(list_a)
list_b.reverse()
print("list_a =" + str(list_a))
print("list_b =" + str(list_b))

Now replace the second line with list_b = list_a and rerun that code. In that case, list_b is the same list as list_a (as opposed to a copy), so when list_b was reversed so is list_a (since list_b is list_a).

Lists can contain arbitrary data types, but if you find yourself doing this, you should probably consider making classes or dictionaries.

confusing_list = ['abc', 1.0, 2, "another string"]
print(confusing_list)
print(confusing_list[3])

range()

range() is a function in Python that automatically generates evenly-spaced integers. Note that the ending value is not included.

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

For non-integer ranges, use numpy.arange from the numpy module.

Note

Prior to Python 3.0, range returned a list of integers. Beginning in 3.0 it returns an object that in many ways behaves like a list but with minimal memory overhead. In Python 3.0 and higher, print(range(3, 7)) displays range(3, 7).

For loops and iterators

We can iterate over elements in a list by following the format: “for element in list:” Notice that indentation is important in Python! After a colon, the block needs to be indented by 4 spaces. (Any consistent indentation will work, but the Python standard is 4).

some_range = range(10)
for elem in some_range:
    print("The value is" + str(elem))

Try substituting some of the previous lists that have been created instead of using some_range and re-evaluate the script block.

The while loop is another type of loop that repeats as long as a condition is True.

If you are ever stuck in a long loop (or any other Python code), try pressing Control-c to break the loop by raising a KeyboardInterrupt exception. Run the following code and stop it by pressing :kdb:`Control-c`:

while True:
    pass

Here, pass means do nothing.

Here we use More advanced looping ~~~~~~~~~~~~~~~~~~~~~

Simulations across time mean that we deal with a lot of time-series data – timestamps and their corresponding values. To simultaneously iterate over two lists of the same size, we can use zip().

y = ['a', 'b', 'c', 'd', 'e']
x = list(range(len(y)))
print("x = {}".format(x))
print("y = {}".format(y))
print(zip(x, y))

Note

Here we have introduced .format . Many usages are possible (see examples on the Python website), but as used here it puts the value of its argument(s) in order into the locations of the string marked by {}.

This is a list of tuples. Given a list of tuples, then we iterate with each tuple.

for x_val, y_val in zip(x, y):
    print("index {}: {}".format(x_val, y_val))

Tuples are similar to lists, except they are immutable (cannot be changed). You can retrieve individual elements of a tuple, but once they are set upon creation, you cannot change them. Also, you cannot add or remove elements of a tuple.

my_tuple = (1, 'two', 3)
print(my_tuple)
print(my_tuple[1])

Attempting to modify a tuple, e.g.

my_tuple[1] = 2

will cause a TypeError.

Because you cannot modify an element in a tuple, or add or remove individual elements of it, it can operate in python more efficiently than a list. A tuple can even serve as a key to a dictionary.

Dictionaries

A dictionary (also called a dict or hash table) is a set of (key, value) pairs, denoted by curly brackets:

about_me = {'name': my_name, 'age': my_age, 'height': "5'8"}
print(about_me)

You can obtain values by referencing the key:

print(about_me['height'])

Similarly, we can modify existing values by referencing the key.

about_me['name'] = "Thomas"
print(about_me)

We can even add new values.

about_me['eye_color'] = "brown"
print(about_me)

We can iterate keys, values or key-value value pairs in the dict. Here is an example of key-value pairs.

for k, v in about_me.items():
    print('key = {}    val = {}'.format(k, v))

To test for the presence of a key in a dict, we just ask:

if 'hair_color' in about_me:
    print("Yes. 'hair_color' is a key in the dict")
else:
    print("No. 'hair_color' is NOT a key in the dict")

Functions

Functions are defined with a “def” keyword in front of them, end with a colon, and the next line is indented. Indentation of 4-spaces (again, any non-zero consistent amount will do) demarcates functional blocks.

def print_hello():
    print("Hello")

Now let’s call our function.

print_hello()

We can also pass in an argument.

def my_print(the_arg):
    print(the_arg)

Now try passing various things to the my_print() function.

my_print("Hello")

We can even make default arguments.

def my_print(the_arg="Hello"):
    print(the_arg)

my_print()
my_print(list(range(4)))

And we can also return values.

def fib(n=5):
    """Get a Fibonacci series up to n."""
    a, b = 0, 1
    series = [a]
    while b < n:
        a, b = b, a + b
        series.append(a)
    return series

print(fib())

Note the assignment line for a and b inside the while loop. That line says that a becomes the old value of b and that b becomes the old value of a plus the old value of b. The ability to calculate multiple values before assigning them allows Python to do things like swapping the values of two variables in one line while many other programming languages would require the introduction of a temporary variable.

You may have noticed the triple-quoted strings. This enables a string to span multiple lines.

multi_line_str = """This is the first line
This is the second,
and a third."""

print(multi_line_str)

The importance of docstrings.

Docstrings are strings just under a function definition, and usually triple-quoted. At the very least, when they exist, they can be used to create beautiful documentation and they can also be available for help in real time. Better yet, they can provide clear examples of using the function and also be used in testing.

help(fib)

Classes

Objects are instances of a “class”. They are useful for encapsulating ideas, and mostly for having multiple instances of a structure. Usually you will have an __init__() method. Also note that every method of the class will have “self” as the first argument. While “self” has to be listed in the argument list of a class’s method, you do not pass a “self” argument when calling any of the class’s methods; instead, you refer to those methods as self.method_name.

class Contact(object):
    """A given person for my database of friends."""

    def __init__(self, first_name=None, last_name=None, email=None, phone=None):
        self.first_name = first_name
        self.last_name = last_name
        self.email = email
        self.phone = phone

    def print_info(self):
        """Print all of the information of this contact."""
        my_str = "Contact info:"
        if self.first_name:
            my_str += " " + self.first_name
        if self.last_name:
            my_str += " " + self.last_name
        if self.email:
            my_str += " " + self.email
        if self.phone:
            my_str += " " + self.phone
        print(my_str)

By convention, the first letter of a class name is capitalized. Notice in the class definition above that the object can contain fields, which are used within the class as “self.field”. This field can be another method in the class, or another object of another class.

Let’s make a couple instances of Contact.

bob = Contact('Bob','Smith')
joe = Contact(email='someone@somewhere.com')

Notice that in the first case, if we are filling each argument, we do not need to explicitly denote “first_name” and “last_name”. However, in the second case, since “first” and “last” are omitted, the first parameter passed in would be assigned to the first_name field so we have to explicitly set it to “email”.

Let’s set a field.

joe.first_name = "Joe"

Similarly, we can retrieve fields from the object.

the_name = joe.first_name
print(the_name)

And we call methods of the object using the format instance.method().

joe.print_info()

Remember the importance of docstrings!

help(Contact)

Importing modules

Extensions to core Python are made by importing modules, which may contain more variables, objects, methods, and functions. Many modules come with Python, but are not part of its core. Other packages and modules have to be installed.

The numpy module contains a function called arange() that is similar to Python’s range() function, but permits non-integer steps.

import numpy
my_vec = numpy.arange(0, 1, 0.1)
print(my_vec)

Note

Numpy is available in many distributions of Python, but it is not part of Python itself. If the import numpy line gave an error message, you either do not have numpy installed or Python cannot find it for some reason. You should resolve this issue before proceeding because we will use numpy in some of the examples in other parts of the tutorial. The standard tool for installing Python modules is called pip; other options may be available depending on your platform.

Pickling objects

There are various file io operations in Python, but one of the easiest is “Pickling”, which attempts to save a Python object to a file for later restoration with the load command.

import pickle
contacts = [joe, bob] # Make a list of contacts

with open('contacts.p', 'wb') as pickle_file: # Make a new file
    pickle.dump(contacts, pickle_file)       # Write contact list

with open('contacts.p', 'rb') as pickle_file: # Open the file for reading
    contacts2 = pickle.load(pickle_file)     # Load the pickled contents

for elem in contacts2:
    elem.print_info()

The next part of this tutorial introduces basic NEURON commands.