Variables, types, scope

micropython___circuitpython_Pigeons-in-holes.jpg
From wikipedia, no creator listed

Quick reference

Variables

Arduino

int a;
int b = 5;

CircuitPython

b = 5

Arrays

Arduino

int a[5];
int a = {1, 2, 3, 4, 5};

CircuitPython

a = [1, 2, 3, 4, 5]

Discussion

Variables

Variables are like little boxes or pigeonholes in computer memory in which you can keep values.

To create a variable in C/C++ you must declare it. That entails giving it a type, a name, and optionally a value.

Download: file
int a;
int b = 5;

In CircuitPython you don't declare variables. They get allocated the first time you assign a value.

Download: file
b = 5

You use the value of a variable in both languages simply by using the name.

Download: file
int x = a_number;    // in C/C++
x = a_number         # in CircuitPython

In both languages, it is an error to use a variable that isn't known: either it hasn't been declared (in C/C++) or initialized (in CircuitPython)

In C/C++, variables are declared with a type, and are statically typed. They only have values of that type.

In contrast, CircuitPython is dynamically typed. Variables do not have an associated type, and can have values of different types at different times. There are advantages and disadvantages to each approach, but in reading someone's CircuitPython code, you should remember this in case someone reused a variable and changed its type.

Collections

Both languages have a way to create and manipulate collections of values.

 

 

 

"Ultimate" - Better than any other collection. Just don't click it.

(image from wikipedia, under fair use license)

Arduino

Arduino has only one way to create collections as part of the language: arrays. Since the language is C++, you are able to use most C++ libraries. With enough CPU performance and memory you can even make use of an Arduino version of the standard C++ and Boost libraries with their collection classes, but for the purposes of this discussion we'll assume you are using the basic language features.

An array is simple a fixed size, fixed order sequence of values. All values in a C array have the same type. In fact, since C is statically typed, the type of the array items is part of the type of the array itself.

For example, if you want to create an array that can hold 5 int values in the variable a, you would declare it as:

Download: file
int a[5];

If the initial values are known at compile-time, you can initialize the array when it's created, and omit the size as it will be inferred from the number of initial values.

Download: file
int a[] = {1, 2, 3, 4, 5};

To access a specific item in an array you use a subscript notation. This can be used to fetch a value out of the array, or to set it to a new value.

Download: file
int x = a[0];
a[1] = x + 1;

CircuitPython

The basic CircuitPython collection is the List, which looks and works a lot like C's array. However, it's a far more dynamic structure: you can freely remove and insert items. It's length is a function of how many items are currently in it, not what it was created to hold. Since they are dynamic, you generally don't need to allocate them in advance. You create a list by enclosing the items in square brackets.

Download: file
>>> a = [1, 2, 3, 4, 5]

As with C arrays, you use a subscript notation to access items.

Download: file
x = a[0]
a[1] = x + 1

CircuitPython's list provides far more functionallity than C arrays, but that's beyond the scope of this guide. CircuitPython also has additional builtin collections: tuples and dictionaries. See this guide on Python's basic data structures and this one more focused on lists and streams.

Both Arduino/C and Python both start groups of elements/arrays at 0. So for a 3 element item, it is x[0], x[1], x[2] unlike some languages like FORTRAN that start arrays at element 1.

Scope

micropython___circuitpython_scope.jpg
From wikipedia by user Elborgo

The scope of a variable is where and when it is available in the code. Both C/C++ and CircuitPython are lexically scoped. That means that a variable's scope is defined by the structure of the code. If you create a variable in a function, it is available in that function but not outside.

Download: file
void f() {
  int a = 5;
  print(a);    // 1
}

print(a);      // 2

The line at 1 is valid since a is in scope. I.e. it's defined in the function f and is available to be used.  The line at 2, however, will cause a compile error because the name a is not known at that point. The situation is similar in CircuitPython:

Download: file
def f():
  a = 5
  print(a)    # 1
  
print(a)      # 2

Local and Global

The code above illustrates local state. I.e. local to a function. That's why referencing the variable from outside the function was a problem. Another scope is global scope. Things with global scope are available throughout the code.

Download: file
int b = 1;

void f() {
  int a = 5;
  print(a + b);
}

print(b);

// prints:
// 6
// 1

This works because b has global scope so it can be used both inside and outside of f.  The same holds in CircuitPython.

Download: file
b = 1

def f():
  a = 5
  print(a + b)
  
print(b)

# prints:
# 6
# 1

What if we have variables with the same name, but in both scopes. Now things start differing. In C/C++ there is no trouble. The variable with the most local scope is the one used.

Download: file
int a = 1;

void f() {
  int a = 5;
  print(a);
}

print(a);

// prints:
// 5
// 1

However, things work quite differently in CircuitPython.

Download: file
a = 1

def f():
  a = 5      # 1
  print(a)
  
print(a)

# prints:
# 5
# 1

The assignment of a is potentially a problem. This is because there is no variable in the local scope named "a", so one will be created because this is the first assignment to a in this scope. For the rest of the function, this local variable a will be used and the variable a in the global scope will be untouched (and unused).

If that's what you want, good. However it's possible that your intent was to change the value of the global a to be 5. If that's the case, then you have a problem.  A tool like pylint will alert you to the fact that the name "a" from an outer scope (the global scope in this case) is being redefined. That will at least draw your attention to it.  If you did want to be using the global a, you can tell CircuitPython that is your intent by using the global statement.

Download: file
a = 1

def f():
  global a
  a = 5      # 1
  print(a)
  
print(a)

# prints:
# 5
# 5

Now pylint might warn you that you're using a global statement. This is because global variables are generally frowned upon. But in a simple script that's fine [in this author's opinion]. In a larger, more structured program they have less use and you have ways to avoid them.

An interesting capability of CircuitPython and C/C++ doesn't share is being able to make closures. This is a somewhat more advanced feature and has been covered in another guide, so find out more there.

This guide was first published on Oct 22, 2018. It was last updated on Oct 22, 2018. This page (Variables, types, scope) was last updated on Oct 25, 2018.