The Python Oracle

Duck typing trouble. Duck typing test for "i-am-like-a-list"

--------------------------------------------------
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------

Music by Eric Matyas
https://www.soundimage.org
Track title: Puzzle Game Looping

--

Chapters
00:00 Duck Typing Trouble. Duck Typing Test For &Quot;I-Am-Like-A-List&Quot;
02:09 Accepted Answer Score 4
03:14 Answer 2 Score 1
04:21 Answer 3 Score 1
05:03 Thank you

--

Full question
https://stackoverflow.com/questions/3033...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #list

#avk47



ACCEPTED ANSWER

Score 4


One thing you can do, that should work quickly on a normal list and fail on a normal dict, is taking a zero-length slice from the front:

try:
    thing[:0]
except TypeError:
    # probably not list-like
else:
    # probably list-like

The slice fails on dicts because slices are not hashable.

However, str and unicode also pass this test, and you mention that you are doing destructive edits. That means you probably also want to check for __delitem__ and __setitem__:

def supports_slices_and_editing(thing):
    if hasattr(thing, '__setitem__') and hasattr(thing, '__delitem__'):
        try:
            thing[:0]
            return True
        except TypeError:
            pass
    return False

I suggest you organize the requirements you have for your input, and the range of possible inputs you want your function to handle, more explicitly than you have so far in your question. If you really just wanted to handle lists and dicts, you'd be using isinstance, right? Maybe what your method does could only ever delete items, or only ever replace items, so you don't need to check for the other capability. Document these requirements for future reference.




ANSWER 2

Score 1


When dealing with built-in types, you can use the Abstract Base Classes. In your case, you may want to test against collections.Sequence or collections.MutableSequence:

if isinstance(your_thing, collections.Sequence):
    # access your_thing as a list

This is supported in all Python versions after (and including) 2.6.

If you are using your own classes to build your_thing, I'd recommend that you inherit from these abstract base classes as well (directly or indirectly). This way, you can ensure that the sequence interface is implemented correctly, and avoid all the typing mess.

And for third-party libraries, there's no simple way to check for a sequence interface, if the third-party classes didn't inherit from the built-in types or abstract classes. In this case you'll have to check for every interface that you're going to use, and only those you use. For example, your list_ish function used __len__ and __getitem__, so only check whether these two methods exist. A wrong behavior of __getitem__ (e.g. a dict) should raise an exception.




ANSWER 3

Score 1


Perhaps their is no ideal pythonic answer here, so I am proposing a 'hack' solution, but don't know enough about the class structure of python to know if I am getting this right:

def is_list_like(thing):
    return hasattr(thing, '__setslice__')

def is_dict_like(thing):
    return hasattr(thing, 'keys')

My reduce goals here are to simply have performant tests that will:

  • (1) never call a dict-thing, nor a string-like-thing a list List item
  • (2) returns the right answer for python types
  • (3) will return the right answer if someone implement a "full" set of core method for a list/dict
  • (4) is fast (ideally does not allocate objects during the test)

EDIT: Incorporated ideas from @DanGetz