Correct way to detect sequence parameter?

2020-01-25 09:52发布

I want to write a function that accepts a parameter which can be either a sequence or a single value. The type of value is str, int, etc., but I don't want it to be restricted to a hardcoded list. In other words, I want to know if the parameter X is a sequence or something I have to convert to a sequence to avoid special-casing later. I could do

type(X) in (list, tuple)

but there may be other sequence types I'm not aware of, and no common base class.

-N.

Edit: See my "answer" below for why most of these answers don't help me. Maybe you have something better to suggest.

12条回答
Animai°情兽
2楼-- · 2020-01-25 10:00

The simplest method would be to check if you can turn it into an iterator. ie

try:
    it = iter(X)
    # Iterable
except TypeError:
    # Not iterable

If you need to ensure that it's a restartable or random access sequence (ie not a generator etc), this approach won't be sufficient however.

As others have noted, strings are also iterable, so if you need so exclude them (particularly important if recursing through items, as list(iter('a')) gives ['a'] again, then you may need to specifically exclude them with:

 if not isinstance(X, basestring)
查看更多
小情绪 Triste *
3楼-- · 2020-01-25 10:03

Revised answer:

I don't know if your idea of "sequence" matches what the Python manuals call a "Sequence Type", but in case it does, you should look for the __Contains__ method. That is the method Python uses to implement the check "if something in object:"

if hasattr(X, '__contains__'):
    print "X is a sequence"

My original answer:

I would check if the object that you received implements an iterator interface:

if hasattr(X, '__iter__'):
    print "X is a sequence"

For me, that's the closest match to your definition of sequence since that would allow you to do something like:

for each in X:
    print each
查看更多
戒情不戒烟
4楼-- · 2020-01-25 10:05

The problem with all of the above mentioned ways is that str is considered a sequence (it's iterable, has getitem, etc.) yet it's usually treated as a single item.

For example, a function may accept an argument that can either be a filename or a list of filenames. What's the most Pythonic way for the function to detect the first from the latter?

Based on the revised question, it sounds like what you want is something more like:

def to_sequence(arg):
    ''' 
    determine whether an arg should be treated as a "unit" or a "sequence"
    if it's a unit, return a 1-tuple with the arg
    '''
    def _multiple(x):  
        return hasattr(x,"__iter__")
    if _multiple(arg):  
        return arg
    else:
        return (arg,)

>>> to_sequence("a string")
('a string',)
>>> to_sequence( (1,2,3) )
(1, 2, 3)
>>> to_sequence( xrange(5) )
xrange(5)

This isn't guaranteed to handle all types, but it handles the cases you mention quite well, and should do the right thing for most of the built-in types.

When using it, make sure whatever receives the output of this can handle iterables.

查看更多
女痞
5楼-- · 2020-01-25 10:05

I think what I would do is check whether the object has certain methods that indicate it is a sequence. I'm not sure if there is an official definition of what makes a sequence. The best I can think of is, it must support slicing. So you could say:

is_sequence = '__getslice__' in dir(X)

You might also check for the particular functionality you're going to be using.

As pi pointed out in the comment, one issue is that a string is a sequence, but you probably don't want to treat it as one. You could add an explicit test that the type is not str.

查看更多
6楼-- · 2020-01-25 10:06

In cases like this, I prefer to just always take the sequence type or always take the scalar. Strings won't be the only types that would behave poorly in this setup; rather, any type that has an aggregate use and allows iteration over its parts might misbehave.

查看更多
爱情/是我丢掉的垃圾
7楼-- · 2020-01-25 10:09

If strings are the problem, detect a sequence and filter out the special case of strings:

def is_iterable(x):
  if type(x) == str:
    return False
  try:
    iter(x)
    return True
  except TypeError:
    return False
查看更多
登录 后发表回答