3

How do we test whether X quacks like a list/tuple?

By that I mean we can possibly subset it by 0 and 1 (etc), though it cannot be a string (which could also be subset).

I thought to test hasattr(X, '__iter__') and not isinstance(X, str), but that would mean a dictionary would still pass which I do not want it to. You could then also test it is not a dictionary, but I wouldn't be so sure about sublasses of dicts etc.

Is there a more official way to test whether something quacks like a list or tuple under this simple specification?

E.g. allowed input should be:

'emailaddr' --> ('emailaddr', 'emailaddr')
('emailaddr', 'alias')
['emailaddr', 'alias']
SequenceThing('emailaddr', 'alias')
PascalVKooten
  • 20,643
  • 17
  • 103
  • 160
  • 2
    What benefit will you gain from the (duck) type check? – Steven Rumbalski Apr 24 '15 at 19:19
  • Seems to me that a string is effectively/informally a tuple anyway. Can you give a practical example of a function where you would want to accept `('a', 'b', 'c')` as an argument, but reject `'abc'`? – Kevin Apr 24 '15 at 19:23
  • I want to have an `('emailaddr@something', 'alias')` tuple, but also accept a list. I'd look up `X[0]` to get the email, and `X[1]` to get the alias. If only a string is given rather than a list or tuple, I'd assume the emailaddress is given and the alias should be equal to the emailaddress (if you can still follow, that would make `(X, X)`) – PascalVKooten Apr 24 '15 at 19:23
  • Include what you would want to be able to pass in to this hypothetical function, as what you're getting at is a bit difficult to infer just from the question alone. – Makoto Apr 24 '15 at 19:25
  • But the thing is, I want to also allow "list-like" and "tuple-like" here, as long as it has index 0 and 1. – PascalVKooten Apr 24 '15 at 19:25
  • `isinstance(thing, collections.Sequence)` almost works, but it returns `True` for strings. – Kevin Apr 24 '15 at 19:25
  • @JoseRicardoBustosM. It is clearly not duplicate because I am wanting to test whether it quacks like either of those, but not necessarily be equal to either. – PascalVKooten Apr 24 '15 at 19:28
  • 1
    @PascalvKooten quacks = "functions as" .... i misunderstood – Jose Ricardo Bustos M. Apr 24 '15 at 19:30
  • If you want things that have `[0]` and `[1]` then you want strings too – Ella Sharakanski Apr 24 '15 at 19:32
  • 3
    @PascalvKooten: check if the value is a string. If so, use it as a string. Otherwise, access the [0] and [1] elements and use them. If you get an exception, you get an exception. No need to check first. – Ned Batchelder Apr 24 '15 at 19:32
  • @NedBatchelder Yea, that looks about right. I'm still curious though if there is not a more official way to test it rather than a solution that works for this specific case. – PascalVKooten Apr 24 '15 at 19:34
  • Possible duplicate http://stackoverflow.com/questions/1835018/python-check-if-an-object-is-a-list-or-tuple-but-not-string – Kos Apr 24 '15 at 19:36

4 Answers4

2

Would you like to check if a value is a sequence?

An iterable which supports efficient element access using integer indices via the getitem() special method and defines a len() method that returns the length of the sequence. Some built-in sequence types are list, str, tuple, and bytes. Note that dict also supports getitem() and len(), but is considered a mapping rather than a sequence because the lookups use arbitrary immutable keys rather than integers.

Old way (deprecated in 2.7):

import operator
print operator.isSequenceType(x)

New way:

import collections
print isinstance(x, collections.Sequence)

They are not equivalent - the latter is more accurate but custom types need to be registered with it.

Also, strings are sequences according to both definitions. You might want to treat basestring specially.

PascalVKooten
  • 20,643
  • 17
  • 103
  • 160
Kos
  • 70,399
  • 25
  • 169
  • 233
2

Don't allow a bare string as an argument; it's a special case waiting to bite you. Instead, document that the input must be in the form of a tuple/list/whatever, but the second element is optional. That is,

['emailaddr', ]
('emailaddr', )
SequenceThing('emailaddr')

are legal, but not

'emailaddr'

This way, your function can assume that X[0] will be an email address, and X[1] may exist and be an alias, but it is the user's problem if they supply something else.

chepner
  • 497,756
  • 71
  • 530
  • 681
1

Duck typing means you just do want you want to do, and if it doesn't work it means you're not dealing with a duck.

If you specifically don't want to get strings, check for strings and raise an exception.

And then do whatever you want to do, no type checking.

Ella Sharakanski
  • 2,683
  • 3
  • 27
  • 47
  • 1
    Exactly, you throw the animal inside of the function, and if no exception is raised, then you got your duck! – bgusach Apr 24 '15 at 20:29
0

A solution based on Kevin's useful comment:

>>> import collections
>>> isinstance([], collections.Sequence) and not isinstance([], str)
True
>>> isinstance((), collections.Sequence) and not isinstance((), str)
True
>>> isinstance('', collections.Sequence) and not isinstance('', str)
False
>>> isinstance({}, collections.Sequence) and not isinstance({}, str)
False
>>> isinstance(set(), collections.Sequence) and not isinstance(set(), str)
False

Mappings such as dicts and sets will not pass the first test. Strings will, but they will obviously fail on the second test.

Shashank
  • 13,713
  • 5
  • 37
  • 63