Skip to content
Python Data Science Toolbox (Part 1)
  • AI Chat
  • Code
  • Report
  • Python Data Science Toolbox (Part 1)

    Scope

    Scope tells you which part of the program an object or a name may be accessed. Names refer to the variables or objects such as functions that are defined in a program. There are three main types of scopes that we should know:

    • Global Scope - A name that is in the global scope means that it is defined in the main body of a script or Python program.

    • Local Scope - A name that is in a local scope means that it is defined within a function. Once the execution of a function is done, any name inside the local scope ceases to exist, which means that you cannot access those names anymore outside of that function.

    • Built-in Scope - This consists of names predefined built-ins module Python provides, such as print() and sum().

    Here we create a function that changes the team name. We use the built-in function global. In Python, the global keyword allows us to modify the variable outside of the current scope. It is used to create a global variable and make changes to the variable in a local context.

    # Create a string: team
    team = "teen titans"
    
    # Define change_team()
    def change_team():
        """Change the value of the global variable team."""
    
        # Use team in global scope
        global team
    
        # Change the value of team in global: team
        team = "justice league"
    # Print team
    print(team)
    
    # Call change_team()
    change_team()
    
    # Print team
    print(team)

    Nested Functions

    Say we have a function inner() defined within another function outer() and we reference a name x in the inner function. When we want to get x, python searches the local scope of inner(), then if it doesn't find x, it searches the scope of outer(), which is called an enclosing function because it encloses inner(). If Python can't find x in the scope of the enclosing function, it only then searches the global scope and then the built-in scope.

    What are nested functions?

    In computer programming, a nested function is a function which is defined within another function, the enclosing function. Due to simple recursive scope rules, a nested function is itself invisible outside of its immediately enclosing function, but can access all local objects (data, functions, types, etc.) of its immediately enclosing function as well as of any functions which, in turn, encloses that function. The nesting is theoretically possible to unlimited depth, although only a few levels are normally used in practical programs.

    Why do we have nested functions?

    Let's say that we want to use a process a number of times within a function. For example, we want a function that takes 3 parameters and performs the same function on each of them. One way would be to write out the computation 3 times but this would be time wasting and not effective if we wanted the program to do this 50 times. What we can do instead is define an inner function within our function definition.

    Realisticly, nested function definitions are a form of information hiding and are useful for dividing procedural tasks into subtasks which are only meaningful locally. This avoids cluttering other parts of the program with functions and variables that are unrelated to those parts.They are typically used as helper functions or as recursive functions inside another function. This has the structural benefit of organizing the code, avoids polluting the scope, and also allows functions to share state easily.

    In this exercise, inside a function three_shouts(), we will define a nested function inner() that concatenates a string object with !!!. three_shouts() then returns a tuple of three elements, each a string concatenated with !!! using inner().

    # Define three_shouts
    def three_shouts(word1, word2, word3):
        """Returns a tuple of strings
        concatenated with '!!!'."""
    
        # Define inner
        def inner(word):
            """Returns a string concatenated with '!!!'."""
            return word + '!!!'
    
        # Return a tuple of strings
        return (inner(word1), inner(word2), inner(word3))
    
    # Call three_shouts() and print
    print(three_shouts('a', 'b', 'c'))

    One other pretty cool reason for nesting functions is the idea of a closure. This means that the nested or inner function remembers the state of its enclosing scope when called. Thus, anything defined locally in the enclosing scope is available to the inner function even when the outer function has finished execution.

    Let's move forward then! In this exercise, we will complete the definition of the inner function inner_echo() and then call echo() a couple of times, each with a different argument..

    # Define echo
    def echo(n):
        """Return the inner_echo function."""
    
        # Define inner_echo
        def inner_echo(word1):
            """Concatenate n copies of word1."""
            echo_word = word1 * n
            return echo_word
    
        # Return inner_echo
        return inner_echo
    
    # Call echo: twice
    twice = echo(2)
    
    # Call echo: thrice
    thrice = echo(3)
    
    # Call twice() and thrice() then print
    print(twice('hello'), thrice('hello'))

    The keyword nonlocal and nested functions

    Let's once again work further on your mastery of scope! In this section, we will use the keyword nonlocal within a nested function to alter the value of a variable defined in the enclosing scope. The nonlocal keyword is used to work with variables inside nested functions, where the variable should not belong to the inner function. Use the keyword nonlocal to declare that the variable is not local.

    # Define echo_shout()
    def echo_shout(word):
        """Change the value of a nonlocal variable"""
        
        # Concatenate word with itself: echo_word
        echo_word = word * 2    
        # Print echo_word
        print(echo_word)
        
        # Define inner function shout()
        def shout():
            """Alter a variable in the enclosing scope"""    
            # Use echo_word in nonlocal scope
            nonlocal echo_word
            
            # Change echo_word to echo_word concatenated with '!!!'
            echo_word = echo_word + '!!!'
        
        # Call function shout()
        shout()
        
        # Print echo_word
        print(echo_word)
    
    # Call function echo_shout() with argument 'hello'
    echo_shout('hello')

    Default Arguements

    In Python, a default parameter is defined with a fallback value as a default argument. Such parameters are optional during a function call. If no argument is provided, the default value is used, and if an argument is provided, it will overwrite the default value. To set a default value in a function, you would have to pass the variable name and assign it to an object. The cool thing is that you can define many default arguements in the same function.

    # Define shout_echo
    def shout_echo(word1, echo=1, intense=False):
        """Concatenate echo copies of word1 and three
        exclamation marks at the end of the string."""
    
        # Concatenate echo copies of word1 using *: echo_word
        echo_word = word1 * echo
    
        # Make echo_word uppercase if intense is True
        if intense is True:
            # Make uppercase and concatenate '!!!': echo_word_new
            echo_word_new = echo_word.upper() + '!!!'
        else:
            # Concatenate '!!!' to echo_word: echo_word_new
            echo_word_new = echo_word + '!!!'
    
        # Return echo_word_new
        return echo_word_new
    
    # Call shout_echo() with "Hey", echo=5 and intense=True: with_big_echo
    with_big_echo = shout_echo("Hey", echo=5, intense=True)
    
    # Call shout_echo() with "Hey" and intense=True: big_no_echo
    big_no_echo = shout_echo("Hey", intense=True)
    
    # Print values
    print(with_big_echo)
    print(big_no_echo)

    Functions with variable-length arguments (*args)

    Flexible arguments enable you to pass a variable number of arguments to a function. In this exercise, we will practice defining a function that accepts a variable number of string arguments.

    The function you will define is gibberish() which can accept a variable number of string values. Its return value is a single string composed of all the string arguments concatenated together in the order they were passed to the function call. We will call the function with a single string argument and see how the output changes with another call using more than one string argument. *Args allows us to pass a variable number of non-keyword arguments to a Python function. In the function, we should use an asterisk ( * ) before the parameter name to pass a variable number of arguments. Args within the function definition is a tuple.

    # Define gibberish
    def gibberish(*args):
        """Concatenate strings in *args together."""
    
        # Initialize an empty string: hodgepodge
        hodgepodge = ""
    
        # Concatenate the strings in args
        for word in args:
            hodgepodge += word
    
        # Return hodgepodge
        return hodgepodge
    
    # Call gibberish() with one string: one_word
    one_word = gibberish("luke")
    
    # Call gibberish() with five strings: many_words
    many_words = gibberish("luke", "leia", "han", "obi", "darth")
    
    # Print one_word and many_words
    print(one_word)
    print(many_words)