Search code examples
pythonpathlib

Having 'and' command inside list comprehension


I do not understand what this source code does on line 3, res = [...]; I tried to understand by testing out in python console with dummy variables and in the same syntax format like res = ['raj' / 'esh']; it gives error; if tested with res = ['raj' and 'esh']; I always return the second string 'esh', so I'm confused why use 'and' in the line. The syntax p/o also confuses me.

def _get_files(parent, p, f, extensions):
    p = Path(p)  #.relative_to(parent)
    res = [p/o for o in f if not o.startswith('.')
           and (extensions is None or f'.{o.split(".")[-1].lower()}' in extensions)]
    return res

Argument p parsed is file path (string), argument f parsed is f = [o.name for o in os.scandir(path) if o.is_file()]; where path in this syntax line is file path. Could I get any help in understanding line 3?


Solution

  • ['raj' and 'esh'] is a one-element array, whose sole element is the result of 'raj' and 'esh'; and will evaluate to the first operand if falsy, and second operand otherwise. Since the first operand is not falsy, you get 'esh'.

    The line in the code is not a simple array, it is a comprehension - basically a short way to write a loop that constructs an array. The general syntax of a comprehension is

    [x for y in z if p]
    

    where y will loop over all elements of the iterable z, check if p is true, and if it is, add x to the result. In your case, the condition (p) is

    not o.startswith('.')
    and
    (extensions is None or f'.{o.split(".")[-1].lower()}' in extensions)
    

    For each element o of f (presumably an iterable of filenames), if this criterion is true, the result list will gain an element which consists of the concatenation of the path p with the filename o (/ being a natural, if surprising at first sight, concatenation operator for paths.)

    The problem is compounded by bad naming exhibited in the snippet. Consider this rewrite:

    def _hidden(filename):
        return filename.startswith('.')
    
    def _extension(filename):
        return '.' + filename.split(".")[-1].lower()
    
    def _extension_ok(filename, allowed_extensions=None):
        return allowed_extensions is None
               or _extension(filename) in allowed_extensions
    
    def _get_files(parent, path, filenames, allowed_extensions=None):
        path = Path(path)
        good_paths = [path/filename for filename in filenames
                      if not _hidden(filename)
                         and _extension_ok(filename, allowed_extensions)]
        return good_paths
    

    Now this reads almost like English, and is very clear about what it's doing (the only dodgy bit being path/filename, and almost anyone could guess what that might be just by analogy to UNIX paths).