Python 3

home

Introduction to Python

davidbpython.com




User-Defined Function Variable Scoping


variable name scoping: the local variable

Variable names initialized inside a function are local to the function.


def myfunc():
    tee = 10
    return tee

var = myfunc()

print(var)          # 10

print(tee)          # NameError ('tee' does not exist here)


Ex. 12.9






variable name scoping: the global variable

Any variable defined in our code outside a function is global.


var = 'hello global'      # global variable

def myfunc():
    print(var)            # this global is available here

myfunc()                  # hello global


Ex. 12.10 - 12.11






"pure" functions

Functions that do not touch outside variables, and do not create "side effects" (for example, calling exit(), print() or input()), are considered "pure" -- and are preferred.


"Pure" functions have the following characteristics:






"pure" functions: working only with "inside" (local) variables

"Outside" (Global) variables are ones defined outside the function -- they should be avoided.


wrong way: referring to an outside variable inside a function

val = '5'                   # defined outside any function

def doubleit():
    dval = int(val) * 2     # BAD:  function refers to "global" variable 'val'
    return dval

new_val = doubleit()

right way: passing outside variables as arguments

val = '5'                   # defined outside any function

def doubleit(arg):
    dval = int(arg) * 2     # GOOD:  refers to the same value '5',
    return dval             #        but accessed through local
                            #        argument 'arg'

new_val = doubleit(val)     # passing variable to function -
                            #   correct way to get a value into the function






"pure" functions: avoiding "side-effects"

print(), input(), exit() all "touch" the outside world and in many cases should be avoided inside functions.



Although it is of course possible (and sometimes practical) to use these built-in functions inside our function, we should avoid them if we are interested in making a function "pure". It should also be noted that it's fine to use any of these in a function during development - they are all useful development tools.






"pure" functions: using raise instead of exit() inside functions

exit() should not be called inside a function.


def doubleit(arg):
    if not arg.isdigit():
        raise ValueError('arg must be all digits')   # GOOD:  error signaled with raise
    dval = int(arg) * 2
    return dval

val = input('what is your value? ')
new_val = doubleit(val)






signalling errors (exceptions) with raise

raise creates an error condition (exception) that usually terminates program execution.



To raise an exception, we simply follow raise with the type of error we would like to raise, and an optional message:

raise ValueError('please use a correct value')

You may raise any existing exception (you may even define your own). Here is a list of common exceptions:

Exception TypeReason
TypeError the wrong type used in an expression
ValueError the wrong value used in an expression
FileNotFoundError a file or directory is requested that doesn't exist
IndexError use of an index for a nonexistent list/tuple item
KeyError a requested key does not exist in the dictionary
Ex. 12.12 - 12.13






global variables and function "purity"

Globals should be used inside functions only in select circumstances.


STATE_TAX = .05    # ALL CAPS designates a "constant"


def calculate_bill(bill_amount, tip_pct):

    tax = bill_amount * STATE_TAX     # int, 5
    tip = bill_amount * tip_pct       # float, 20.0

    total_amount = bill_amount + tax + tip   # float, 125.0

    return total_amount


total = calculate_bill(100, .20)      # float, 125.0






"pure" functions: why prefer them?

Here are some positive reasons to strive for purity.


You may have noticed that these "impure" practices do not cause Python errors. So why should we avoid them?



The above perspective will become clearer as you write longer programs. As your programs become more complex, you will be confronted with more complex errors that are sometimes difficult to trace. Over time you'll realize that the best practice of using pure functions enhances the "quality" of your code -- making it easier to write, maintain, extend and understand the programs you create. Again, please note that during development it is perfectly allowable to call print(), exit() or input() from inside a function. We may also decide on our own that this is all right in shorter programs, or ones that we working on in isolation. It is with longer programs and collaborative projects where purity becomes more important.






proper code organization

Let's discuss some essential elements of a program.


Here are the main components of a properly formatted program. Please Sse the tip_calculator.py file in your files directory for an example:


review of tip_calculator.py






the four variable scopes: L-E-G-B

Four kinds of variables: (L)ocal, (E)nclosing, (G)lobal and (B)uiltin.


filename = 'pyku.txt'       # 'filename':  global

                            # 'get_text':  global (function name is a
                            #                      variable as well)
def get_text(fname):        # 'fname':     local
    fh = open(fname)        # 'fh':        local; 'open':  builtin
    text = fh.read()        # 'text':      local
    return text

txt = get_text(filename)    # 'txt':       global
print(txt)                  # 'print':     builtin