11

I have a function like this one

def print_stuff(items):
    if isinstance(items, (str, bytes)):
        items = (items,)
    for item in items:
        print (item)

that can be called as follows:

In [37]: print_stuff(('a', 'b'))
a
b

In [38]: print_stuff('a')
a

I don't like doing isinstance (items, (str, bytes)) I would prefer to do isinstance(item, (collections.abc.MAGIC))

where MAGIC is a ABC of all the sequence objects that can contain other sequence objects such as

  • tuple
  • list
  • numpy.array
  • some user defined vector class, etc

but not:

  • str
  • bytes
  • some user defined str class for UTF-16, etc

I am afraid this is impossible as tuple and str have the same 7 ABCs :(

In [49]: [v for k, v in vars(collections.abc).items()
    ...:                                   if inspect.isclass(v) and issubclass(tuple, v) ]
Out[49]:
[collections.abc.Hashable,
 collections.abc.Iterable,
 collections.abc.Reversible,
 collections.abc.Sized,
 collections.abc.Container,
 collections.abc.Collection,
 collections.abc.Sequence]

In [50]: [v for k, v in vars(collections.abc).items()
    ...:                                   if inspect.isclass(v) and issubclass(list, v) ]
Out[50]:
[collections.abc.Iterable,
 collections.abc.Reversible,
 collections.abc.Sized,
 collections.abc.Container,
 collections.abc.Collection,
 collections.abc.Sequence,
 collections.abc.MutableSequence]

In [51]: [v for k, v in vars(collections.abc).items()
    ...:                                   if inspect.isclass(v) and issubclass(str, v) ]
Out[51]:
[collections.abc.Hashable,
 collections.abc.Iterable,
 collections.abc.Reversible,
 collections.abc.Sized,
 collections.abc.Container,
 collections.abc.Collection,
 collections.abc.Sequence]
jcr
  • 1,015
  • 6
  • 18
  • Your usage example is not clear; what output do you expect from *print_stuff("ab")*? – guidot Mar 14 '17 at 10:30
  • Why didn't you start question with a capital letter? – Piotr Dobrogost Mar 14 '17 at 10:35
  • 2
    Possible duplicate of [How to check if type of a variable is string?](http://stackoverflow.com/questions/4843173/how-to-check-if-type-of-a-variable-is-string) – Piotr Dobrogost Mar 14 '17 at 10:48
  • @guidot I expect/wish it to work with user defined Sequences that does not implicit inherit from an ABC, The way I understand ABCs in python 3.x is that they can be inferred based on the __magi__ methods a class have, thus if a difference between the two sets of Sequences existed, then you should be able to separate them.... as I state in my question I doubt that this is possible, and from the answers it seems I have co be content `str` or write my own __subclasshook__ – jcr Mar 14 '17 at 11:36
  • @PiotrDobrogost that solution is my question, I am doing isinstance(item, str), but am not happy with it as it will not work for user defined string classes, but an ABC should in principle be able to separate thise, though in practice it seems to be only possible (and hakish at best) via __subclasshook__ – jcr Mar 14 '17 at 11:48
  • @jcr It seems to me, that you focuse strongly on an implementation without a clearly stated problem. For all purposes beyond string analysis itself string is the same atomar data type as int, for which I see neither a special handling in your code. (Just an existing enumerator returning its characters is no counter-proof). Special requirements in that direction surely justify visible explicit code on the caller side (as use of an own tuple derivation). – guidot Mar 14 '17 at 12:41
  • @guidot I expect print_stuff("ab") to return the same as print_stuff(("ab")), I want my function to be called with both a Sequence/iterable or a string, but ducktype the string into a iterable with 1 element to get consistent behaviour. I often need this for convenience and was therefore wondering if I could do this if instance using an abc, to ensure it works with all types of strings not just the built in python `str` object – jcr Mar 14 '17 at 14:13

2 Answers2

5

Good question.

  • There is (currently) no ABC that distinguishes a string from a tuple or other immutable sequence; since there is just one string type in Python 3, the most Pythonic solution is indeed with an isinstance(x, str).
  • Byte sequence types such as bytes and bytearray can be distinguished using the collections.abc.ByteString ABC.

Of course, you could also define your own ABC which includes both str and ByteString, or even give it a __subclasshook__ that checks classes for a method such as capitalize.

Uri Granta
  • 1,814
  • 14
  • 25
  • __subclasshook__ is an amazing idear, I will not do it because it will be overkill for my code. was really hoping there was a better way :( – jcr Mar 14 '17 at 11:49
0

Your magic is called types.StringTypes. It is not abstract base class but it is the best we have.

From https://docs.python.org/2/library/types.html#types.StringTypes (emphasis mine):

A sequence containing StringType and UnicodeType used to facilitate easier checking for any string object. Using this is more portable than using a sequence of the two string types constructed elsewhere since it only contains UnicodeType if it has been built in the running version of Python. For example: isinstance(s, types.StringTypes).

New in version 2.2.

Piotr Dobrogost
  • 41,292
  • 40
  • 236
  • 366
  • 5
    types.StringTypes is not available in python 3.x – jcr Mar 14 '17 at 11:34
  • Any equivalent or similar for Python3? I mean, besides `isinstance(item, (Mapping, Sequence)) and not isinstance(item, (str, ByteString))`? – MestreLion Oct 30 '21 at 01:36