Python - too many values to unpack while looking for a file

Viewed 86

I'm trying to get a message that file not found when it doesn't match the file_name*.txt pattern in specific directory.

When calling the script with file_name*.txt argument, all works fine. While entering invalid name file_*.txt throws:

File "etl.py", line 14, in main
path, file = file_path.get_source_file_path()
ValueError: too many values to unpack (expected 2)

Why is this happening?

import fnmatch
import os

class FilePrep:
    def __init__(self, path_dir, file_name):
        self.path = path_dir
        self.file_name = file_name

    def get_source_file_path(self):
        source_file = []

        for file_name in os.listdir(self.path):
            if fnmatch.fnmatch(file_name, self.file_name):
                source_file.append(file_name)
        try:            
            source_file_name = str(source_file[0])
        except IndexError:
            return "file not found"
        
        file_path = os.path.join(self.path, source_file_name)
        return file_path, source_file_name

main.py

import file_prep
import xml.etree.ElementTree as element_tree
import pandas
import sys

def main():

    dir = sys.argv[1]
    file_input = sys.argv[2]

    #python3 etl.py /home/user/projects/python/folder/ file_name*.xml
    file_path = file_prep.FilePrep(dir, file_input)

    path, file = file_path.get_source_file_path()
    print(path)
4 Answers

Your problem is in this line in main.py:

path, file = file_path.get_source_file_path()

here you are unpacking the return value of get_source_file_path() into two variables. This works fine if the file exists (because you actually return two values) but does not when the file does not exist since you only return one value return "file not found".

To fix this I would raise an error instead of returning a string message in case of failure. Your code can become:

import fnmatch
import os

class FileNotFoundError(Exception):
    pass

class FilePrep:
    def __init__(self, path_dir, file_name):
        self.path = path_dir
        self.file_name = file_name

    def get_source_file_path(self):
        source_file = []

        for file_name in os.listdir(self.path):
            if fnmatch.fnmatch(file_name, self.file_name):
                source_file.append(file_name)
        try:            
            source_file_name = str(source_file[0])
        except IndexError:
            raise FileNotFoundError(f"file {self.file_name} not found")
        
        file_path = os.path.join(self.path, source_file_name)
        return file_path, source_file_name
import file_prep
import xml.etree.ElementTree as element_tree
import pandas
import sys

def main():

    dir = sys.argv[1]
    file_input = sys.argv[2]

    #python3 etl.py /home/user/projects/python/folder/ file_name*.xml
    file_path = file_prep.FilePrep(dir, file_input)

    try:
        path, file = file_path.get_source_file_path()
        print(path)
    except file_prep.FileNotFoundError as e:
        print(e)

The previous answers somewhat got it the wrong way, saying in case of failure, "only one" value is returned. While that's true, it doesn't explain why the error message says there are "too many values to unpack", since 1 is not more than 2. The reason is that that one value is a string, which, due to to the multi-assignment, will be treated as an iterable. Since it has 14 characters, that's 14 values, and that's too many.

The issue is this following line:

except IndexError:
    return "file not found"

When you hit that except condition your return from your function call returns only one variable, that string. You could modify your condition to do the following:

except IndexError:
    source_file_name = "file not found"

However this won't work as you try to use this file name later on, a better approach would be to raise an error properly as shown in this answer by Matteo here.

When you call the function here, it's expected that two values will be returned

path, file = file_path.get_source_file_path()

When an exception occurs here, only one value is being returned which is causing the error

try:            
    source_file_name = str(source_file[0])
except IndexError:
    return "file not found"

There are two ways to fix this.

  1. Make it so only one value is expected to be returned and you modify the return at the bottom to only pass one value
  2. You modify the exception so it returns two values
Related