8

I have a list as below:

device = [('nvme2n1',), 
          ('nvme1n1', '/local'), 
          ('nvme0n1',), 
          ('nvme0n1p1', '/'),
          ('nvme0n1p128',), 
          ('nvme3n1',)]

I want to delete few tuples from this list containing nvme1n1 or nvme0n1p1 or nvme0n1p128 or nvme0n1.

so the final list will have

final_device = [('nvme2n1',),('nvme3n1',)]

tried as below but didn't work & got error "AttributeError: 'tuple' object has no attribute 'startswith'"

for word in devices[:]: 
    if word.startswith("nvme0n1","nvme0n1p1","nvme0n1p128"): 
        devices.remove(word)

Can anyone help with this?

Georgy
  • 12,464
  • 7
  • 65
  • 73
dbNovice
  • 399
  • 2
  • 4
  • 18

5 Answers5

7
devices = [('nvme2n1',), ('nvme1n1', '/local'),
           ('nvme0n1',), ('nvme0n1p1', '/'), 
           ('nvme0n1p128',), ('nvme3n1',)]
devices = [device for device in devices if device[0] not in 
           (("nvme0n1", "nvme0n1p1", "nvme0n1p128", "nvme0n1"))]
print(devices)

output

[('nvme2n1',), ('nvme1n1', '/local'), ('nvme3n1',)]

@JamesTollefson in their answer address the particular problem in your code and how to remedy it. This is just different and in my opinion better/cleaner way to achieve what you want.

buran
  • 13,682
  • 10
  • 36
  • 61
5

The words you are iterating through in the devices list are tuples. You need to access that part of that tuple you are interested in as follows:

for word in devices[:]: 
    if word[0] in ["nvme0n1","nvme0n1p1","nvme0n1p128"]: 
        devices.remove(word)
James Tollefson
  • 883
  • 3
  • 14
  • Nice answer that addresses the problem. There's a typo, though, `devices[:]` should simply be `device`. Tried to propose an edit, but edits have to be at least 6 chars D= – Asker Nov 07 '20 at 07:05
  • 1
    It works the way I wrote it, but you're definitely right that it's cleaner this way. Thanks @Asker. I made the change you recommended. – James Tollefson Nov 07 '20 at 07:30
  • 3
    @Asker @JamesTollefson the `[:]` is actually necessary for it to work, otherwise you're modifying the list while iterating it which will cause some elements to be skipped. Try it with `devices = [(1,), (2,), (3,), (4,)]` and `if word[0] in [1, 2, 3]: ...` - the resulting list is `[(2,), (4,)]`, i.e. `(2,)` doesn't get removed. This behavior is somewhat described [here](https://stackoverflow.com/questions/6260089/strange-result-when-removing-item-from-a-list-while-iterating-over-it) and [here](https://stackoverflow.com/questions/1207406/how-to-remove-items-from-a-list-while-iterating). – Czaporka Nov 07 '20 at 15:06
  • 1
    @Czaporka Good point, I didn’t consider that! Sorry JamesTollefson for the faulty edit suggestion – Asker Nov 07 '20 at 17:42
  • Interesting, @Czaporka, I didn't know about that. – James Tollefson Nov 07 '20 at 19:57
5

You can even use a list comprehension to make it simpler. As James mentioned in his answer, you need to do word[0] as word is a tuple and not a string.

startswith can take a tuple to check with.

[dev for dev in devices if not dev[0].startswith(("nvme0n1","nvme0n1p1","nvme0n1p128"))]

But if you're looking for exact matches, you can do,

[dev for dev in devices if dev[0] not in ("nvme0n1","nvme0n1p1","nvme0n1p128")]
Durga Swaroop
  • 563
  • 1
  • 6
  • 25
2

@JamesTollefson's answer is nice and gets to the heart of the matter.

As a side note, just wanted to add that when dealing with lists and dicts, you can make your code more concise and elegant using list comprehensions:

devices = [d in devices if d[0] not in {"nvme0n1", "nvme0n1p1", "nvme0n1p128", "nvme0n1"}]

Asker
  • 1,299
  • 2
  • 14
  • 31
0

The question is a bit vague on exactly what criteria is to be measured.

E.g. does it have to be an exact match , contain or start with.

Below is a comprehensive solution I made to come up with four cases and solutions.

There is also the additional element of matching anything in a tuple if the index is unknown.

If you find any issues with the code/have suggestions I'll happily edit.

There's also future proofing so using the list of items to be removed inline and not setting it to a variable should be avoided. This solution is therefore more general.

Using the below with: final_device = remove_from_tuple_list(device,remove_list,0,match_type = 'contains')

Solves the OP's specific problem.

The code is fully commented with explantions.

device = [('nvme2n1',), 
          ('nvme1n1', '/local'), 
          ('nvme0n1',),
          ('nvme0n1p1', '/'),
          ('nvme0n1p128',),
          ('nvme3n1',)]

remove_list = ['nvme1n1', 'nvme0n1p1' , 'nvme0n1p128', 'nvme0n1']

def analyze_item_criteria(item : str ,criteria : list,criteria_type : str):
    '''
    Check whether an item string or tuple is contained in a list of items

    Args:
        item : the item to be evaluated
        criteria : list of items to be checked against
        criteria_type: the type of criteria

    Returns:
        Boolean True/False if evaluated
        None if not evaluated
    
    Raises:
        'Invalid criteria_type specified' : if the item is not a valid criteria type

    '''
    ##Check that type is valid
    assert criteria_type in ['contains',
                          'exact',
                          'starts_with',
                          'ends_with',
                          ],'Invalid criteria_type specified'

    if criteria_type == 'exact':
        if item in criteria:
            return True
        else:
            return False
    ##Starts with/ends with also takes a tupe (.startswith((args,))) this implementation
    ##Isn't really different from what happens under the hood
    elif criteria_type == 'starts_with':
        if any([item.startswith(x) for x in criteria] ):
            return True
        else:
            return False
    elif criteria_type == 'ends_with':
        if any([item.endswith(x) for x in criteria] ):
            return True
        else:
            return False
    elif criteria_type == 'contains':
        if any([x in item for x in criteria]):
            return True
        else:
            return False
#Specifying no index does not really work with well exact but the other three options are still valid
def remove_from_tuple_list(tuple_list : list, remove_list :list, tuple_index : int = None, match_type : str = 'contains'):
    '''
    Function to remove items from a list of tuples containing specific words.
    The function can remove items at a specified index or any position if not specified

    Args:
        tuple_list : The list of tuples from which items are to be removed (items in tuple have to be str/int/float , objects will
                      have unexpected behaviour)
        remove_list : list of strings/items to be removed from the tuple_list
        tuple_index : if an integer, checks for criteria at the specific position otherwise the whole tuple which is inefficient
                      Set to none if you want to analyze a list of strings for example as well
        match_type : Do we want to match the word exactly or do we look for it containing the word
                      There are a few cases to consider here.
                      - contains
                      - exact
                      - starts_with
                      - ends_with
    Returns:
        list of items not in remove_list

    Raises:
        'Invalid match_type specified' : if the item is not in the match list
    '''

    ##Check that type is valid
    assert match_type in ['contains', 
                          'exact',
                          'starts_with',
                          'ends_with',
                          ],'Invalid match_type specified'
    new_tuple_list = []
    #Check whether we are looking at a specific index in the tuple

    ##This can be done multiple ways, think analysing the if once is the most efficient so justifies the additional typing
    if isinstance(tuple_index,int):
        new_tuple_list = [
                            list_item for list_item in tuple_list 
                            if not analyze_item_criteria(list_item[tuple_index],remove_list,match_type)
                        ]
    else:
        new_tuple_list = [list_item for list_item in tuple_list if not analyze_item_criteria('|'.join([str(x) for x in list_item])
        ,remove_list,match_type)]

    return new_tuple_list





def test():
    final_device = remove_from_tuple_list(device,remove_list,0,match_type = 'contains')
    print(remove_from_tuple_list(device,remove_list,0,match_type = 'contains'))
    print(remove_from_tuple_list(device,remove_list,0,match_type = 'exact'))
    remove_list = ['nvme1n1', 'nvme0n1p1', 'nvme0n1']
    print(remove_from_tuple_list(device,remove_list,0,match_type = 'ends_with'))
    print(remove_from_tuple_list(device,remove_list,0,match_type = 'starts_with'))


    print(remove_from_tuple_list(device,remove_list,None,match_type = 'contains'))
    print(remove_from_tuple_list(device,remove_list,None,match_type = 'exact'))
    remove_list = ['nvme1n1', 'nvme0n1p1', 'nvme0n1']
    print(remove_from_tuple_list(device,remove_list,None,match_type = 'ends_with'))
    print(remove_from_tuple_list(device,remove_list,None,match_type = 'starts_with'))

    '''
    Output:
    -------
    [('nvme2n1',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme0n1p128',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme1n1', '/local'), ('nvme0n1p1', '/'), ('nvme0n1p128',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme1n1', '/local'), ('nvme0n1p1', '/'), ('nvme0n1p128',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme3n1',)]
    '''