Search code examples
pythonpython-3.xrecursionexeclocals

Dynamically assigning variable with locals() isn't working in recursive function


I have a recursive script that's scraping a JSON file for cars. At each recursive level, it gets a new variable added, and passes that (along with the other values) on to the recursive call, each time getting more and more detailed in the information. I tried to use locals() to dynamically assign a variable, but it remains None even after the call (I recall seeing that sometimes locals() is read only).

I tried using eval() as well, and it gives me the same issue (I know eval is not ideal). I'd ideally like to avoid using a dictionary, because that would require me to load it with values first, which seems like it has some unnecessary steps, but I'm open to anything at this point.

Example:

scraper(manufacturer='Honda') would scrape a JSON file of models, set model='Accord' and then recursively call

scraper(manufacturer='Honda, model='Accord') which scrapes a file of years, set's year=2014 and recursively calls

scraper(manufacturer='Honda', model='Accord', year='2014') which is the base case

def scraper(self, manufacturers, model=None, year=None):

    if year:
        scrapeurl = '%s&manufacturer=%s&model=%s&year=%s' % (url, manufacturer, model, year)
        return someFinalFunction()

    elif model:
        scrapeurl = '%s&manufacturer=%s&model=%s' % (url, manufacturer, model)

    elif manufacturer:
        scrapeurl = '%s&manufacturer=%s' % (url, manufacturer)

    j = getJSONFromUrl(scrapeurl)
    key, values = j.popitems()

    for value in values:
        locals()[key] = value
        return self.scraper(manufacturer, model, year, color)

I'd appreciate any input on how to handle this, I know Python always seems to have some clever ways of doing things, and I'm always learning more about it, so thank you in advance! I'm using Python3 in this example too, if that changes anything


Solution

  • It's not entirely clear what you're trying to do, but perhaps this will help:

    def scraper(self, **kwargs):
    
        if kwargs.get('year') is not None:
            scrapeurl = '{0}&manufacturer={manufacturer}&model={model}&year={year}'
            return someFinalFunction() # not sure why this takes no arguments
    
        elif kwargs.get('model') is not None:
            scrapeurl = '{0}&manufacturer={manufacturer}&model={model}'
    
        elif kwargs.get('manufacturer') is not None:
            scrapeurl = '{0}&manufacturer={manufacturer}'
    
        else:
            raise KeyError
    
        j = getJSONFromUrl(scrapeurl.format(url, **kwargs))
        key, values = j.popitems()
    
        for value in values:
            kwargs[key] = value
            return self.scraper(**kwargs)
    

    This uses Python's built-in functionality to treat arbitrary keyword arguments as a dictionary, along with more modern str.format string formatting, to dynamically handle the arguments you're looking for. The only difference is that you now need to call it:

    instance.scraper(manufacturer='...')
    

    rather than just

    instance.scraper('...')
    

    An example of the string formatting, mixing positional and keyword arguments:

    >>> '{0}&manufacturer={manufacturer}'.format('foo', **{'manufacturer': 'bar'})
    'foo&manufacturer=bar'