Tuesday, August 6, 2019

nonlocal keyword

The nonlocal keyword is used to allow an inner function to access variables defined in an outer function. Without using the nonlocal keyword a new variable will be created in the inner function and will have no effect on the outer function's variables.

The below example demonstrates this by defining two versions of s. The first s is accessible only to the outer function say_hello and the other s is only accessible to the inner function say_it.


def say_hello():
    s = "Hello from outer function say_hello"
    def say_it():
        s = "Hello from inner function say_it"
        print(s)
    print(s)
    say_it()

say_hello()

The below code demonstrates how to access the outer function's local variable s. By using the nonlocal keyword, say_hello and say_it are sharing the variable s.


def say_hello():
    s = "Hello from outer function say_hello"
    def say_it():
        nonlocal s
        s = "Hello from inner function say_it"
    
    print(s) #print s before it is modified by say_it
    say_it() #change the value of s
    print(s) #print the new value after its modified by say_it

say_hello()

The concept is very similar to the usage of the global keyword. The difference is global is used to allow a function access to variables defined as global ( actually module attributes ) while nonlocal allows access to variables defined in outer functions.

Monday, August 5, 2019

Importing Global Variables in Python

Global variables in python are actually considered module level variables or module attributes and are not attached to a global namespace. This leads to some confusion when trying to change them from another module.

Attempting to change them can lead to some quirky behavior. This is one of the few flaws that I see in an otherwise elegant and well implemented programming language.

To demonstrate a common trap in python module attributes make a new file called hello.py and type the code below:

g_hello = "Hello World"

def print_g_hello():
    print(g_hello)
hello.py - declaring g_hello global and print_g_hello function

When you import hello into another python script you will be able to access the variable g_hello and it seems like you can change it but really your changes only take effect within your current script. Changes to g_hello will not effect the value within hello.py or other modules that you import the global with.The below code will demonstrate the problem.


from hello import g_hello, print_g_hello

print(g_hello)
g_hello = "test" #this seems to work but actually does not effect g_hello outside of this script
print(g_hello)

#using the keyword global still has no effect on the global
#it is only changed within the context of this script.
def change_hello():
    global g_hello
    g_hello = "Hello changed!"

change_hello()
print(g_hello)  #again hello changed local to this script
print_g_hello() #calls a function in hello to print g_hello showing it is unchanged
test.py - importing g_hello and print_g_hello to demonstrate global scope

In the above script we import the global using the syntax from hello import g_hello. This seems like the most common sense approach to access a global declared in another python script. However, you will get read-only access to that global variable. If that is all you need, which is usually the case with config scripts, there is no issue.

Problems arise when you find yourself trying to modify the values. As shown in the above example you will actually end up with two different values depending on how or where the variable is called.

You can change the variable if you access it using the module name instead of importing the variable separately. This is a little strange as you would expect the import statement to simply serve, like other languages, as an alias. However, importing an entire module will access its attributes directly while importing each attribute individually in fact creates a copy of that attribute that does not modify the module.

Create a new file called test2.py to demonstrate changing the global variable g_hello.

import hello #import the module so we can access its globals
from hello import print_g_hello #import the function to test if it has been changed

print(hello.g_hello)
hello.g_hello = "World"
print(hello.g_hello) #hello is changed

print_g_hello() #hello is changed within the hello module as well
test2.py - importing g_hello and print_g_hello to demonstrate changing a global

This behavior is actually intended by the makers of python to try and curb the problems with global scope. Usage of global variables are frowned upon in most programming circles and module level variables are not any better as they just attach a namespace to a globally scoped variable.

The most common usage of globals in python is for configuration files. This usage is usually condoned because passing configuration to every module or class in your solution would unnecessarily add to the code and complexity of your code.


Wednesday, July 3, 2019

Python lambda

A lambda function in python allows you to write a reusable expression. Unlike saving the result of an expression in a variable the expression is re-evaluated every time it is used.The simplest lambda function in python takes no arguments and simply returns a literal like the one below:

x = lambda : 100
print(x())
Notice that when x is declared with the lambda keyword it has to be called by using parenthesis.

The above example isn't anymore useful then just assigning x the value of 100. However, the benefit of using lambdas over variables is they are dynamic. That is they will always evaluate to the expression instead of the last value entered.

The lambda expression is a little easier for most people to understand because it works like you would expect a + b to behave in a mathematical equation.


a = 100
b = 1
x = lambda : a + b

print(x())

a = 10
b = 10
print(x())
We don't have to reassign x because a and b are re-evaluated on every call

Output:
101
20

In the above example I don't have to re-assign x like I would with a variable because it always re-evaluates the value of a and b before giving a result. A lambda function in python must return a value. They are intended to evaluate mathematical expressions and calling functions that return a value. However, because many functions return a value but otherwise do not produce a usable value lambdas are often used for non-expression calls. Python's print function, for example, returns None. Because it returns a value it can be used in a lambda as if it where an expression:


print_me = lambda : print("Hello World")
print_me()

Python lambdas can take zero or more arguments and don't need to be assigned to a variable. They are often passed into functions so that a named function doesn't have to be declared. This is often used when a function takes another function as a parameter.


lst = [1, 2, 2, 2]
my_map = map(lambda x : x * 2, lst)
print(list(my_map))

The map function, shown above, is a common use case of a lambda. It applies the expression passed as a lambda to the second argument which is a list of values and applies the expression to the list. Because the above function, x * 2, doesn't have to be named lambda is often referred to as an anonymous function.

The most common usage of the lambda is for callbacks. Callbacks are often used in User Interface libraries in order to perform an action on click.


MyButton = UIButton("Click Me")
MyButton.OnClick(lambda sender, state : print("You clicked me!"))

There is another way to accomplish the above expression using function definitions. It is a little extra syntax because you have to use the keyword return in order to actually have x() actually return a value. It also has to have a name which makes function definitions not very anonymous.

a = 10
b = 10
def x(): return a + b
print(x())
Still very compact but needs a name and a return value

Lambdas allow you to write python code in a functional style rather than object oriented or procedural. Python has an advantage over purely functional languages, that make it difficult to do things like save states or perform loops in that you can solve things that make sense with functional features and still write procedural or object oriented code.


I use the term procedural programming a little lightly. I think the term is still accurate as to how many programs are written today. However, its usage has dropped in popularity and is often replaced by the term sequential programming. Procedural code is usually in reference to older or "legacy" code.




Example - global keyword

You can declare a global variable in python easily enough but when you try to change it from a function no error is displayed and the old value is still there.

#declare a global variable
a = 23

def print_a_variable():
    print(a) #Printing it works just fine


def change_a_variable():
    a = 100000
    print(a) #seems to work but a is actually a new variable


print_a_variable()
change_a_variable()
print(a)

Output:
23
100000
23

If you use the keyword global in your function then the global is no longer read-only.


#declare a global variable
a = 23

def print_a_variable():
    #global a - not needed here because we can have readonly access to globals
    print(a) #Printing a works just fine


def change_a_variable():
    global a #If you want to change the global then it has to be declared here
    a = 100000
    print(a) #a is declared local and doesn't effect the global variable a


print_a_variable()
change_a_variable()
print(a) #a is still 23

Output:
23
100000
100000

Sunday, June 30, 2019

The for in Loop In Python

One of the strongest features, in my opinion, that Python has is its ability to work with sequences. Sequences are ranges, lists, tuples, sets and dictionaries. I think that pythons elegance in working with these sequences is due to the design decision not to include extra syntax.

Most languages have two for statements...the for loop and the for in loop. Python only uses the for in loop and has a built in function called range (xrange in python 2.7) to do basic indexing.

for i in range(10): print(i)
Because python uses zero indexes the above example prints 0-9 and not 1-10. Ranges actually stop at count-1.

It's tempting to use the above form of the for loop to iterate or cycle through all indexes of a list as the below example shows but there are better ways to accomplish the same task.

lst = [0,1,2,3,4,5,6,7,8,9]
for i in range(len(10)): 
  print(i)
This is not the most pythonic or cleanest way to loop through a list.


Counting indexes and passing the length to the range function is just a little too much work and is really unnecessary because you can accomplish the same thing by passing the list directly into the for in loop.

for value in [0,1,2,3,4,5,6,7,8,9]: 
  print(value)
I just passed a literal list into the above code but I could have passed in a list variable like I do in the below examples.

For most cases the above syntax is all that is needed and I estimate that I use it 90% of the time and rarely use any other looping mechanism. I suspect that the gate keepers of Python had the same experience and that is why they chose only two loop structures ( while and for in ). Programmers coming from a different language such as C++, C# or Java are usually quick to resort to the range function because they need access to the index not just the value. However, python has a better solution to this problem by using the built-in function enumerate.

The below example uses the enumerate function to iterate through all values in a list with the for in loop and print the index and the value accordingly.


>>> lst = [10, 3, -1, 40, 9, 1, 0, 3, 3]
>>> for index, value in enumerate(lst):
...   print("index: " + str(index) + " value: " + str(value))
... 
index: 0 value: 10
index: 1 value: 3
index: 2 value: -1
index: 3 value: 40
index: 4 value: 9
index: 5 value: 1
index: 6 value: 0
index: 7 value: 3
index: 8 value: 3
>>> 
This implementation would be blessed by the python elders and priests.

The same task using the range function, as shown below, is a little more wordy and is more difficult to debug.


>>> lst = [10, 3, -1, 40, 9, 1, 0, 3, 3]
>>> for index in range(len(lst)):
...   print("index: " + str(index) + " value: " + str(lst[index]))
... 
index: 0 value: 10
index: 1 value: 3
index: 2 value: -1
index: 3 value: 40
index: 4 value: 9
index: 5 value: 1
index: 6 value: 0
index: 7 value: 3
index: 8 value: 3
>>> 
This would be considered non-pythonic because it is not the most readable and compact implementation.

The former example using enumerate is most likely to get a thumbs up from fellow python developers but the later is probably going to give you a little bit of push back. I would prefer the enumerate implementation as well but because I have written a good number of C, COBOL, C++ and C# programs I don't judge either one as being good or bad. Its important to note that many developers are very good at looping through collections and they may not even see the benefit in the enumerate function.

The for in range structure  is still a useful construct and is needed if we want to:
  1. Iterate over a fixed number without indexing a collection. 
  2. Calling an internal or external API that gives us a count and a "get item" method instead of a collection.
  3. Skipping or "stepping" through index values.
Ranges can actually be saved into a variable, count backwards, step by positive and negative numbers and can be converted to other sequences like lists and tuples. The range function and xrange function are actually implemented differently and have a different processing cost. Even though their usage may be rare they do belong in python!

More on ranges can be found online here:
https://pynative.com/python-range-function/

A good book on python such as the one below can also be a valuable resource in learning python.

if, if..else, if..elif..else

When you need to execute code if and only if a certain condition is met then you can use the if statement. If statements can be very simple or very complex.

if

In it's simplest form an if statement in python looks like if a == b: <<statement>>

if a == b: a = 100
One liner if statements are sometimes pointed at as not pythonic due to readability. But I believe this is unfounded when the statement is very short.

Indentation is very important when writing structures such as if statements in python. If we expand the above statement to two lines the if statement only executes lines that are indented.

if a == b: 
  a = 100
  b = 0
  c = random(1,0)
d = 1 #always executes 

if..else

You can also use the keyword else that will only execute if the condition in the conditional expression is false. You can only have one else in an if statement. The following example will always print hello because the condition is the literal value True.


if False:
  pass
else:
  print("Hello")
The pass keyword in python just tells python to do nothing. Many programming languages allow you to write a comment or simply leave a blank line but python requires an empty if to at least have pass statement in its body.

You can put several if statements together if you have multiple conditions and statements to run.

if s == "Hello":
  print("World")

if s2 == "":
  print("The string is empty")

if s3 == "abc"
  print("I know my abcs")

if..elif..else

If you want to check multiple conditions but only if the previous condition(s) are false then use the if..elif structure. The following example is very similar to the above series of if statements but each condition is only evaluated if the previous condition is false.

if s == "Hello":
  print("World")
elif s2 == "":
  print("The string is empty")
elif s3 == "abc":
  print("I know my abcs")
The keyword elif just means else if. The makers of python just wanted to save three stroke on a keyboard!

The else statement must go at the end of this series of statements. It will be executed if all if and elif conditions are false. For example:

if s == "Hello":
  print("World")
elif s2 == "":
  print("The string is empty")
elif s3 == "abc":
  print("I know my abcs")
else:
  print("Nothing else was true!")

Backwards If Statements

You can also put an if statement proceeding another statement in python. Unlike the first example this version of a one liner if statement is considered good form and is very useful when converting raw data into something readable. The else is required in this case because otherwise the variable has no assignment if the condition is false.


s = "Not Entered" if s == "" else s
Notice that you don't need the colon (:) in this statement

You may be tempted to add an elif to the above example but it will not work. The elif statement doesn't apply to this if structure.


#This will not run. The elif keyword is not valid here
s = "Not Entered if s == "" elif s == "*" "Err" else s

Nested If Statements

In python, and in most modern programming languages for that matter, you can nest or place if statements within if statements for more complex conditions.

a = 1
b = 1
c = 1
d = 1
if a == b:
  if a == c:
    if a == d:
      print("a b c and d are all equal")
else:
  print("a is not equal to b")

The above example is pretty lengthy and is for demonstration purposes. It can be written instead, without nested if statements, as:

a = b = c = d = 1
if a == b == c == d:
   print("a b c and d are all equal")
else:
   print("a is not equal to b")

You can also nest if, if..elif..else statements inside of the else and elif statement bodies like this:


a = b = c = d = 10
if a == b:
  if a == c:
    pass
  elif a == d:
    pass
elif a == 0:
  if a != 1:
    pass
  else:
    print("Hello!")
else:
  print("Hello")

Nesting if statements too deep can lead to unreadable code. Sometimes it is the best solution to the problem but I find that is very rare. It is not often that I need to nest an if statement more than two levels deep in any programming language.

COBOL programs are notorious for having a "dangling if" problem. Reading deeply nested if statements in COBOL is very difficult because its hard to tell, in a very nested if structure, what if statement goes to what else or else if. Python doesn't have this problem because it uses indentation. You can easily see what if, elif, and if statements go together because they are on the same column.

Saturday, June 29, 2019

Python Conditional Expressions

Python supports conditional expressions. Conditional expressions allow you to execute statements only when a specific condition is met. Conditions are mathematical expressions such as:

a == 0         #Is a equal to zero?
a != 0         #Is a equal to zero?
a > 100        #Is a greater than 100
a >= 100       #Is a greater than or equal to 100
a < c          #Is a less than c
a <= c         #Is a less than or equal to c

Conditionals can be used outside of an if, while or other conditional statement. If you set the variable a to a value you can use the above expressions to evaluate the result. The below program sets a to 0 and then evaluates its condition


>>> a = 0
>>> a == 0
True
>>> a < 0
False
>>> a <= 0
True
>>> a > 0
False
>>> a == 10000
False
>>> 

Conditional expressions evaluate to a boolean type. If you pass them to the built-in type function you can see they are boolean objects.

>>> type(a == 0)
<class 'bool'>
>>> 

Because conditional expressions can be evaluated to a variable, really an object, we can store the conditional for later use.

>>> is_a_equal_to_zero = a == 0
>>> is_a_equal_to_zero
True
>>> 

Once the variable is_a_equal_to_zero is assigned it stores the last known evaluation of a == 0 and not what it currently is. If you re-assign a to a new value it will not change is_a_equal_to_zero.


>>> is_a_equal_to_zero = a == 0
>>> is_a_equal_to_zero
True
>>> a = 10000
>>> is_a_equal_to_zero
True
>>> 

Conditional Expressions are fundamental in programming and it is difficult to get any "real work" done without having some kind of conditional logic. You can combine conditional expressions with if statements, while loops and other python structures.

nonlocal keyword

The nonlocal keyword is used to allow an inner function to access variables defined in an outer function. Without using the nonlocal keywo...