7

I have a function that gets a list of DB tables as parameter, and returns a command string to be executed on these tables, e.g.:

pg_dump( file='/tmp/dump.sql',
         tables=('stack', 'overflow'),
         port=5434
         name=europe)

Should return something like:

pg_dump -t stack -t overflow -f /tmp/dump.sql -p 5434 europe

This is done using tables_string='-t '+' -t '.join(tables).

The fun begins when the function is called with: tables=('stackoverflow') (a string) instead of tables=('stackoverflow',) (a tuple), which yields:

pg_dump -t s -t t -t a -t c -t k -t o -t v -t e -t r -t f -t l -t o -t w
        -f /tmp/dump.sql -p 5434 europe

Because the string itself is being iterated.

This SO question suggests using asserts on the type, but I'm not sure it's Pythonic enough because it breaks the duck-type convention.

Any insights?

Adam

2
  • You are referring to a user using ('foo') by mistake instead of ('foo',), right? Commented Nov 21, 2010 at 10:53
  • @orip That's the point: Users might do this mistake, and I want to prevent it from happening. Commented Nov 21, 2010 at 13:35

5 Answers 5

6

Asserting the type seems appropriate in this case - handling a common misuse that seems legal because of duck typing.

Another way to handle this common case would be to test for string and handle it correctly as a special case.

Finally, you could encourage passing the table names as positional parameters which would make this scenario less likely:

def pg_dump(*tables, **kwargs):
  file = kwargs['file']
  port = kwargs['port']
  name = kwargs['name']
  ...

pg_dump('stack', 'overflow', file='/tmp/dump.sql', port=5434, name='europe')
Sign up to request clarification or add additional context in comments.

1 Comment

+1 Nice. I think I'll test the parameter, and simply convert a string to a 1-tuple. It would keep the users happier.
3

You can use ABCs to assert that an object is iterable but not a string:

from types import StringType
from collections import Iterable
assert isinstance(x, Iterable) and not isinstance(x, StringType)

Comments

1

A common Python idiom to detect whether an argument is a sequence (a list or tuple) or a string is to check whether it has the __iter__ attribute:

def func(arg):
    if hasattr(arg, '__iter__'):
        print repr(arg), 'has __iter__ attribute'
    else:
        print repr(arg), 'has no __iter__ attribute'

func('abc')
# 'abc' has no __iter__

func(('abc'))
# 'abc' has no __iter__

func(('abc',))
# ('abc',) has __iter__

When it's not a sequence, it's also common to change it into one to simplify the rest of the code (which only has to deal with one kind of thing). In the sample it could have been done with a simple arg = [arg].

4 Comments

Sidenote: strings not having an __iter__ attribute was an inconsistency and has been changed in Python 3. as it should be.
@Jim Brissom: I always felt this was inconsistent since strings are after all sequences of characters. Know a good PY3K idiom? How about checking to see if the type(arg) has a string method, like 'lower`?
you can check for not isinstance(foo, str) like suggested in the question the asker linked to (basestring instead of str for Python < 3)
@orip: Sure, one can always type-check, but as the OP indicated, that doesn't seem very "Pythonic". It would be nice if there was a generic way to check whether something was a sequence or not that was confused by string types -- that what the __iter__ test does in Python 2 but sounds like they "fixed" that in v3.
0

Can you not use a list rather than a tuple?

pg_dump( file='/tmp/dump.sql',
         tables=['stack', 'overflow'],
         port=5434,
         name='europe')

1 Comment

Doesn't solve the problem - if the user passes a string it still fails.
0

The cleanest way I can think of is to create a new Abstract Collection type "NonStrSequence" by overriding subclasshook. See below implementation and tests:

  from typing import Sequence, ByteString
  from abc import ABC
  class NonStrSequence(ABC):
      @classmethod
      def __subclasshook__(cls, C):
          # not possible to do with AnyStr
          if issubclass(C, (str, ByteString)):
              return NotImplemented
          else:
              return issubclass(C, Sequence)



  tests = {
      'list_of_strs': ['b', 'c'],
      'str': 'abc',
      'bytes': b'bytes',
      'tuple': ([1,2], 'a'),
      'str_in_parens': ('a'), # Not a tuple
      'str_in_tuple': ('a',),
      }
  for type in [Sequence, NonStrSequence]:
      for k,v in tests.items():
          print(f'{k}: isinstance({v}, {type}): {isinstance(v, type)}')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.