Search code examples
pythonpython-2.7optimizationconceptual

pass method agument to local instance object for filter list


I would like to convert argument in prent method to filter list result with filter of an local instance object.

That is what I would like approximatively for the concept

def get_fields(path, editable=True): # If only editable (or opposite)
    return list([f.name for f in fields(path) if f.editable]) # Do filter
def get_fields(path, required=True): # If only required (or opposite)
    return list([f.name for f in fields(path) if f.required]) # Do filter

def get_fields(path): # If all
    return list([f.name for f in fields(path)])

This is what i do actualy

def get_fields_editable(path):
    return list(f.name for f in fields(path) if f.editable]))

def get_fields_required(path):
    return list(f.name for f in fields(path) if f.required]))

def get_fields(path):
    return list(f.name for f in fields(path)]))

Solution

  • We can use operator.attrgetter to create a suitable filtering function from the keyword arg name and the associated value.

    To demonstrate this code I created a simple Test object, and a fields function.

    from operator import attrgetter
    
    def get_fields(path, **kwargs):
        # Create the filter function
        # Assumes there will only ever be at most a single keyword arg
        if kwargs:
            key, val = kwargs.popitem()
            func = attrgetter(key)
            filter_func = func if val else lambda obj: not func(obj)
        else:
            filter_func = lambda obj: True
    
        return [f.name for f in fields(path) if filter_func(f)]
    
    class Test(object):
        def __init__(self, name, editable, required):
            self.name = name
            self.editable = editable
            self.required = required
    
    def fields(path):
        return [
            Test(path + '_a', False, False),
            Test(path + '_b', False, True),
            Test(path + '_c', True, False),
            Test(path + '_d', True, True),
        ]
    
    print(get_fields('none'))
    print(get_fields('edfalse', editable=False))
    print(get_fields('edtrue', editable=True))
    print(get_fields('reqfalse', required=False))
    print(get_fields('reqtrue', required=True))
    

    output

    ['none_a', 'none_b', 'none_c', 'none_d']
    ['edfalse_a', 'edfalse_b']
    ['edtrue_c', 'edtrue_d']
    ['reqfalse_a', 'reqfalse_c']
    ['reqtrue_b', 'reqtrue_d']
    

    Here's a new version that handles multiple keywords. Only items that pass all of the filter tests are returned. I've changed the test object names slightly and added a print call to get_fields to make it easier to see what's going on. This code works on both Python 2 and Python 3.

    from __future__ import print_function
    from operator import attrgetter
    
    # Return items that pass any of the filters
    def get_fields_any(path, **kwargs):
        seq = fields(path)
        print('KW', kwargs)
        result = set()
        if kwargs:
            for key, val in kwargs.items():
                # Create the filter function
                func = attrgetter(key)
                filter_func = func if val else lambda obj: not func(obj)
                # Apply it
                result.update(filter(filter_func, seq))
        else:
            result = seq
    
        return [f.name for f in result]
    
    # Only return items that pass all filters
    def get_fields(path, **kwargs):
        seq = fields(path)
        print('KW', kwargs)
        if kwargs:
            for key, val in kwargs.items():
                # Create the filter function
                func = attrgetter(key)
                filter_func = func if val else lambda obj: not func(obj)
                # Apply it
                seq = list(filter(filter_func, seq))
        return [f.name for f in seq]
    
    class Test(object):
        def __init__(self, name, editable, required):
            self.name = name
            self.editable = editable
            self.required = required
    
        def __repr__(self):
            fmt = 'Test({name}, {editable}, {required})'
            return fmt.format(**self.__dict__)
    
    def fields(path):
        return [
            Test(path + ':FF', False, False),
            Test(path + ':FT', False, True),
            Test(path + ':TF', True, False),
            Test(path + ':TT', True, True),
        ]
    
    print(get_fields('__'))
    print(get_fields('_F', required=False))
    print(get_fields('_T', required=True))
    print(get_fields('F_', editable=False))
    print(get_fields('T_', editable=True))
    print()
    
    print(get_fields('FF', editable=False, required=False))
    print(get_fields('FT', editable=False, required=True))
    print(get_fields('TF', editable=True, required=False))
    print(get_fields('TT', editable=True, required=True))
    

    output

    KW {}
    ['__:FF', '__:FT', '__:TF', '__:TT']
    KW {'required': False}
    ['_F:FF', '_F:TF']
    KW {'required': True}
    ['_T:FT', '_T:TT']
    KW {'editable': False}
    ['F_:FF', 'F_:FT']
    KW {'editable': True}
    ['T_:TF', 'T_:TT']
    
    KW {'editable': False, 'required': False}
    ['FF:FF']
    KW {'editable': False, 'required': True}
    ['FT:FT']
    KW {'editable': True, 'required': False}
    ['TF:TF']
    KW {'editable': True, 'required': True}
    ['TT:TT']
    

    Note that this code can handle any number of keywords, not just two.


    I've also included an experimental function get_fields_any which returns the objects that pass any of the filters. It uses a set result to accumulate the matching objects, so it doesn't preserve order. Also, putting objects that don't define __eq__ and __hash__ methods into sets (or using them as dict keys) is risky. See the __hash__ docs for details.