4

I have a rather large project that I'm trying to clean up before posting, but when I run flake8 I get tons of

'F405 <function> may be undefined, or defined from star imports: generic_functions`

I could avoid this by replacing the line:

from generic_functions import *

at the start of my file, but:

  1. I literally use all of the functions in there, so I don't understand how it's more pythonic to exceed the 80 character limit typing out every single function:

     from generic_functions import (function1, function2, function3, function4, function5, function6, function7...)
    
  2. Doing the above would be tedious, especially if I need to add or remove from the dozens of functions in generic_functions.py

The other option would be to disable the F405 warning, but what if the function is truly undefined? It would be nice to allow star imports while still catching anything undefined. Is there a way to do this? I tried adding # noqa to the import line, but it doesn't seem to help.

SurpriseDog
  • 462
  • 8
  • 18
  • Follow up: Ultimately I fixed this issue by writing this program to auto-generate the * import lines for me: https://github.com/SurpriseDog/Star-Wrangler – SurpriseDog Sep 14 '21 at 22:48

2 Answers2

5

You can do the following:

  1. Leave your import as from generic_functions import *
  2. Run flake8 as flake8 --ignore=F405 file.py. I really don't know why # noqa doesn't work, but --ignore does.
  3. Test the rest of possible errors with pylint. Pylint is able to determine that <function> is defined and imported from generic_functions.
sanyassh
  • 8,100
  • 13
  • 36
  • 70
  • Is it considered bad form to leave in the "from generic_functions import *" line when you use all of the functions? I've searched this question online and see a lot of hate for star imports. – SurpriseDog May 22 '19 at 23:24
  • 1
    You maybe interested in this topic: https://stackoverflow.com/questions/2386714/why-is-import-bad. I would say that sometimes it is not so bad to have `import *` if changing it to `import name1, name2, ...` will lead to a huge number of imports. – sanyassh May 22 '19 at 23:32
0

You can use pylint to determine which functions are required and which ones are not. I wrote a function to automate this process. It works as follows:

  1. Run pylint on a script and match all of the W0614: Unused import errors.
  2. Get the list of variables in the target module with vars()
  3. Take the difference of those sets and only return things that can be imported.

import subprocess, types

def scrape_wildcard(filename, modvars):
    "Get variables imported from module in wild * import"
    error = "W0614: Unused import "
    unused = []
    for line in quickrun(['pylint', filename]):
        if error in line:
            unused.append(line.split(error)[1].split()[0])

    out = dict()
    for name in set(modvars.keys()) - set(unused):
        if not name.startswith('__'):
            func = modvars[name]
            if not isinstance(func, types.ModuleType):
                out[name] = modvars[name]
    return out


def quickrun(cmd, check=False, encoding='utf-8', errors='replace'):
    ret = subprocess.run(cmd, stdout=subprocess.PIPE, check=check)
    return ret.stdout.decode(encoding=encoding, errors=errors).splitlines()


import ????? as mymod
filename = "?????"
print('from', mymod.__name__, 'import', ', '.join(scrape_wildcard(filename, vars(mymod)).keys()))
'''

Replace the ????? with your module name and the filename of the module, respectively.

SurpriseDog
  • 462
  • 8
  • 18