Search code examples
pythonreferencecommandcommand-line-interfacecd

Cannot access variable from outer scope (Python)


So I'm trying to make a CLI but I'm having some errors with the 'cd' command. I can't access the 'target_path' variable from my cd command. Here's the code for the CLI:

import os, colorama

ERROR_COLOR = colorama.Back.RED + colorama.Fore.BLACK

user = os.getlogin()
target_path = f'C:\\Users\\{user}'

class Commands:
    def __init__(self):
        raise RuntimeError("Cannot instantiate class 'Commands'")


    @staticmethod
    def help(cmd=None):
        cmd_help_msgs = {
            'help': \
    """
    HELP:
        USAGE:
            help [command: str]

        DESCRIPTION:
            Prints out the description and usage of a command.
            If command is not passed, it prints out the help messages of all commands
    """,
            'cls': \
    """
    CLS:
        USAGE:
            cls
            
        DESCRIPTION:
            Clears the terminal
    """,
            'exit': \
    """
    EXIT:
        USAGE:
            exit
        
        DESCRITPION:
            Quits the program
    """,
        'echo': \
    """
    ECHO:
        USAGE:
            echo <msg: str>
            
        DESCRIPTION:
            Prints out msg
    """
        }

        help_msg = f"""
IMPORTANT: [] = optional parameter, <> = mandatory parameter

COMMANDS:
"""

        if cmd is None:
            print(help_msg, end='')
            for val in cmd_help_msgs.values():
                print(val, end='')
        else:
            try:
                print(help_msg, end='')
                print(cmd_help_msgs[cmd])
            except:
                print(f"{ERROR_COLOR}'{cmd}' is not recognized as a valid command! Do 'help' to see all valid commands{colorama.Style.RESET_ALL}")
                return

        print('\n')


    @staticmethod
    def cls():
        os.system('cls')


    @staticmethod
    def exit():
        exit(0)


    @staticmethod
    def echo(msg=''):
        if msg == '':
            print(ERROR_COLOR + "Missing required argument 'msg'" + colorama.Style.RESET_ALL)
            return

        print(msg)


    @staticmethod
    def cd(directory=''):
        if directory == '':
            print(target_path)
            return

        if directory[1] != ':':
            if os.path.exists(target_path + directory) and os.path.isdir(target_path + directory):
                target_path += directory
            else:
                print(f"{directory} is not a valid directory")
        else:
            if os.path.exists(directory) and os.path.isdir(directory):
                target_path = directory
            else:
                print(f"{directory} is not a valid directory")


command_list = [func for func in dir(Commands) if callable(getattr(Commands, func)) and not func.startswith('__')]

os.system('cls')

while True:
    command_has_kws = False

    command = input(f'[{target_path}]{colorama.Fore.CYAN} @ {colorama.Fore.LIGHTBLUE_EX}').strip().split(' ')
    print(colorama.Style.RESET_ALL, end='')

    for i in range(len(command)):
        command[i] = command[i].strip()

        if command[i].startswith('-'):
            command_has_kws = True

    if command[0] in command_list:
        try:
            if not command_has_kws:
                getattr(Commands, command[0])(*command[1:])
            else:
                # REMINDER: Type 'getattr(Commands, command[0]).__code__.co_varnames[:getattr(Commands, command[0]).__code__.co_argcount]' to get the parameters of a command
                pass # TODO: Implement keywords
        except TypeError:
            print(f"{ERROR_COLOR}Too many or too little arguments were passed to '{command[0]}'{colorama.Style.RESET_ALL}")
    else:
        print(f"{ERROR_COLOR}'{command[0]}' is not recognized as a valid command! Do 'help' to see all valid commands{colorama.Style.RESET_ALL}\n")

Here's 2 things you should focus on:

target_path = f'C:\\Users\\{user}'

And:

@staticmethod
    def cd(directory=''):
        if directory == '':
            print(target_path)
            return

        if directory[1] != ':':
            if os.path.exists(target_path + directory) and os.path.isdir(target_path + directory):
                target_path += directory
            else:
                print(f"{directory} is not a valid directory")
        else:
            if os.path.exists(directory) and os.path.isdir(directory):
                target_path = directory
            else:
                print(f"{directory} is not a valid directory")

Also here's the error message:

Traceback (most recent call last):
  File "C:\Users\[REDACTED]\Desktop\Crabby_CLI\main.py", line 132, in <module>
    getattr(Commands, command[0])(*command[1:])
  File "C:\Users\[REDACTED]\Desktop\Crabby_CLI\main.py", line 102, in cd
    if os.path.exists(target_path + directory) and os.path.isdir(target_path + directory):
UnboundLocalError: local variable 'target_path' referenced before assignment

INFORMATION: Interpreter - Python 3.8 OS - Windows 10 Editor - PyCharm Community Edition


Solution

  • local variable 'target_path' referenced before assignment this error suggests that you are trying to access target_path in one of your functions before assigning any value and if my understanding is correct, you want to use the global target_path, that you have set in top, within those functions.

    If this is correct, then you need to tell your functions to use global variables.

    Solution :

    Within each function that uses the 'target_path' variable, in the beginning, put this line

    def abc():
        # add this line
        global target_path
        ...
        ...
        # now use `target_path` here