52

A function should select rows in a table based on the row name (column 2 in this case). It should be able to take either a single name or a list of names as arguments and handle them correctly.

This is what I have now, but ideally there wouldn't be this duplicated code and something like exceptions would be used intelligently to choose the right way to handle the input argument:

def select_rows(to_select):
    # For a list
    for row in range(0, table.numRows()):
        if _table.item(row, 1).text() in to_select:
            table.selectRow(row)
    # For a single integer
    for row in range(0, table.numRows()):
        if _table.item(row, 1).text() == to_select:
            table.selectRow(row)
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Steven Hepting
  • 12,394
  • 8
  • 40
  • 50

6 Answers6

32

Actually I agree with Andrew Hare's answer, just pass a list with a single element.

But if you really must accept a non-list, how about just turning it into a list in that case?

def select_rows(to_select):
    if type(to_select) is not list: to_select = [ to_select ]

    for row in range(0, table.numRows()):
        if _table.item(row, 1).text() in to_select:
            table.selectRow(row)

The performance penalty for doing 'in' on a single-item list isn't likely to be high :-) But that does point out one other thing you might want to consider doing if your 'to_select' list may be long: consider casting it to a set so that lookups are more efficient.

def select_rows(to_select):
    if type(to_select) is list: to_select = set( to_select )
    elif type(to_select) is not set: to_select = set( [to_select] )

    for row in range(0, table.numRows()):
        if _table.item(row, 1).text() in to_select:
            table.selectRow(row)
Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
NickZoic
  • 7,575
  • 3
  • 25
  • 18
  • 14
    It is currently considered better to use `isinstance` rather than `type`, see https://stackoverflow.com/questions/1549801/what-are-the-differences-between-type-and-isinstance – Dr_Zaszuś Mar 20 '18 at 11:49
  • And if using `isinstance`, there are 2/3-compatibility concerns for certain types. For example, string types are str (in 3) or basestring (in 2.7). You can use `six.string_types` or `six.integer_types` for this. Unfortunately, there's no `six.float_types` so if you need `long` from Python 2.7 you'll have to code that manually. – Dakota Aug 27 '20 at 18:21
29

You could redefine your function to take any number of arguments, like this:

def select_rows(*arguments):
    for row in range(0, table.numRows()):
        if _table.item(row, 1).text() in arguments:
            table.selectRow(row)

Then you can pass a single argument like this:

select_rows('abc')

multiple arguments like this:

select_rows('abc', 'def')

And if you already have a list:

items = ['abc', 'def']
select_rows(*items)
Steef
  • 33,059
  • 4
  • 45
  • 36
  • +1 Like this approach better than Andrew Hare's... Problem could be if you needed to pass more arguments to the same function, not just the list/single argument. But you could either have those before, or use keyword arguments, i.e. **kwargs. – Jaime Jun 16 '09 at 01:58
  • 1
    This answer is clearly better. +1 Self-documenting code. *args begs for an iterable. – tortal Aug 19 '16 at 06:27
12

I would do just this:

def select_rows(to_select):
    # For a list
    for row in range(0, table.numRows()):
        if _table.item(row, 1).text() in to_select:
            table.selectRow(row)

and expect that the argument will always be a list - even if its just a list of one element.

Remember:

It is easier to ask for forgiveness than permission.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
  • 1
    +1... much easier to maintain just one set of code to performs a task, and more pythonic; let it explode if someone calls it in defiance of the docs. If a function is truly needed that accepts a single integer as an argument, make a second one called 'def select_row(to_select)' and have it package 'to_select' as a list, then call select_rows. – Jarret Hardie Jun 16 '09 at 00:12
  • @JarretHardie : "let it explode if someone calls it in defiance of the docs" ... as a refugee from C#, I find this attitude in python programming the most befuddling :-D – butterflyknife Nov 24 '21 at 20:12
2

I'd go along with Sharkey's version, but use a bit more duck typing:

def select_rows(to_select):
    try:
        len(to_select)
    except TypeError:
        to_select = [to_select]

    for row in range(0, table.numRows()):
        if _table.item(row, 1).text() in to_select:
            table.selectRow(row)

This has the benefit of working with any object that supports the in operator. Also, the previous version, if given a tuple or some other sequence, would just wrap it in a list. The downside is that there is some performance penalty for using exception handling.

DopplerShift
  • 5,472
  • 1
  • 21
  • 20
  • 1
    This one is problematic for unicodes and strings. cf: http://stackoverflow.com/questions/305359/correct-way-to-detect-sequence-parameter/425567#425567 – Gregg Lind Jun 16 '09 at 21:50
  • Valid point, should at least have been "in (list, tuple)" ... or maybe "not in (string, unicode)". Preferably you'd want to look directly for "does this thingy support 'in'", I suppose. – NickZoic Jun 17 '09 at 03:46
  • 1
    Actually, Python doesn't really have a performance penalty for exception handling like other languages. See, for instance: https://jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/ – Bobort Dec 06 '18 at 16:53
0

A simple wrapper could do, to handle list or single object case

  def wrap_list(val):
    if type(val) is list:
      return val
    return [val] 
FlashDD
  • 418
  • 4
  • 14
-1

A bit less general but concise, using numpy:

numpy.unique()

handles that automatically by keeping only the unique values of an array-like or scalar, and returns a list of the unique values, or of the scalar.

import numpy as np

def select_rows(to_select):
   to_select = np.unique(to_select)

   for row in range(0, table.numRows()):
       if _table.item(row, 1).text() in to_select:
           table.selectRow(row)