代码之家  ›  专栏  ›  技术社区  ›  ire_and_curses

在Python中处理单值元组的最佳实践是什么?

  •  10
  • ire_and_curses  · 技术社区  · 15 年前

    我使用的是第三方库函数,它从一个文件中读取一组关键字,并且应该返回一组值。只要至少有两个关键字,就可以正确地执行此操作。但是,在只有一个关键字的情况下,它返回一个原始字符串,而不是一个大小为1的元组。这是特别有害的,因为当我尝试做像

    for keyword in library.get_keywords():
        # Do something with keyword
    

    ,对于单个关键字, for 在运行时或其他情况下,连续迭代字符串的每个字符,这不会引发异常,但对我来说是完全无用的。

    我的问题有两个方面:

    显然,这是库中的一个bug,超出了我的控制。我怎样才能最好地解决这个问题?

    其次,一般来说,如果我正在编写一个返回元组的函数,那么确保使用一个元素正确生成元组的最佳实践是什么?例如,如果我有

    def tuple_maker(values):
        my_tuple = (values)
        return my_tuple
    
    for val in tuple_maker("a string"):
        print "Value was", val
    
    for val in tuple_maker(["str1", "str2", "str3"]):
        print "Value was", val
    

    我明白了

    Value was a
    Value was  
    Value was s
    Value was t
    Value was r
    Value was i
    Value was n
    Value was g
    Value was str1
    Value was str2
    Value was str3
    

    修改函数的最佳方法是什么 my_tuple 当只有一个元素时返回元组?是否显式地需要检查大小是否为1,并使用 (value,) 语法?这意味着任何有可能返回单值元组的函数都必须这样做,这看起来很糟糕而且重复。

    这个问题是否有一些优雅的一般解决方案?

    8 回复  |  直到 6 年前
        1
  •  15
  •   Lennart Regebro    15 年前

    如果是字符串或元组,您需要以某种方式测试类型。我会这样做:

    keywords = library.get_keywords()
    if not isinstance(keywords, tuple):
        keywords = (keywords,) # Note the comma
    for keyword in keywords:
        do_your_thang(keyword)
    
        2
  •  7
  •   MAK    15 年前

    对于您的第一个问题,我不确定这是否是最佳答案,但我认为您需要检查自己返回的值是字符串还是元组,并相应地进行操作。

    对于第二个问题,任何变量都可以通过放置 , 旁边:

    >>> x='abc'
    >>> x
    'abc'
    >>> tpl=x,
    >>> tpl
    ('abc',)
    

    把这两个想法放在一起:

    >>> def make_tuple(k):
    ...     if isinstance(k,tuple):
    ...             return k
    ...     else:
    ...             return k,
    ... 
    >>> make_tuple('xyz')
    ('xyz',)
    >>> make_tuple(('abc','xyz'))
    ('abc', 'xyz')
    

    注意:imho使用isInstance或任何其他形式的逻辑在运行时需要检查对象类型通常是一个坏主意。但对于这个问题,我看不出任何解决办法。

        3
  •  2
  •   me_and    15 年前

    你的 tuple_maker 不会像你想象的那样。一个等价的定义 tuple maker 对你来说是

    def tuple_maker(input):
        return input
    

    你看到的是 tuple_maker("a string") 返回字符串,而 tuple_maker(["str1","str2","str3"]) 返回字符串列表;两者都不返回元组!

    python中的元组是由逗号而不是括号来定义的。因此 (1,2) 是包含值的元组 1 2 ,同时 (1,) 是包含单个值的元组 .

    如其他人指出的,要将值转换为元组,请使用 tuple .

    >>> tuple([1])
    (1,)
    >>> tuple([1,2])
    (1,2)
    
        4
  •  2
  •   Will McCutchen    15 年前

    总是有蒙凯补丁!

    # Store a reference to the real library function
    really_get_keywords = library.get_keywords
    
    # Define out patched version of the function, which uses the real
    # version above, adjusting its return value as necessary
    def patched_get_keywords():
        """Make sure we always get a tuple of keywords."""
        result = really_get_keywords()
        return result if isinstance(result, tuple) else (result,)
    
    # Install the patched version
    library.get_keywords = patched_get_keywords
    

    注: 这个密码可能会烧毁你的房子和你的妻子睡觉。

        5
  •  1
  •   Josh Wright    15 年前

    我不检查长度为1,而是使用isInstance内置的。

    >>> isinstance('a_str', tuple)
    False
    >>> isinstance(('str1', 'str2', 'str3'), tuple)
    True
    
        6
  •  1
  •   Epcylon    15 年前

    它是否绝对有必要返回元组,或者任何iterable都会这样做?

    import collections
    def iterate(keywords):
        if not isinstance(keywords, collections.Iterable):
            yield keywords
        else:
            for keyword in keywords:
                yield keyword
    
    
    for keyword in iterate(library.get_keywords()):
        print keyword
    
        7
  •  0
  •   Anycorn    15 年前

    对于第一个问题,可以使用

    type(r) is tuple
    #alternative
    isinstance(r, tuple)
    # one-liner
    def as_tuple(r): return [ tuple([r]), r ][type(r) is tuple]
    

    我喜欢用的第二件事 tuple([1]) . 认为这是一个品味问题。例如,还可以编写包装器 def tuple1(s): return tuple([s])

        8
  •  0
  •   SkyLeach    6 年前

    当使用tuple()构造函数方法而不是用于创建单个字符串tuple的默认类型定义时,需要注意一件重要的事情。下面是一个nose2/unittest脚本,您可以使用它来处理问题:

    #!/usr/bin/env python
    # vim: ts=4 sw=4 sts=4 et
    from __future__ import print_function
    # global
    import unittest
    import os
    import sys
    import logging
    import pprint
    import shutil
    
    # module-level logger
    logger = logging.getLogger(__name__)
    
    # module-global test-specific imports
    # where to put test output data for compare.
    testdatadir = os.path.join('.', 'test', 'test_data')
    rawdata_dir = os.path.join(os.path.expanduser('~'), 'Downloads')
    testfiles = (
        'bogus.data',
    )
    purge_results = False
    output_dir = os.path.join('test_data', 'example_out')
    
    
    def cleanPath(path):
        '''cleanPath
        Recursively removes everything below a path
    
        :param path:
        the path to clean
        '''
        for root, dirs, files in os.walk(path):
            for fn in files:
                logger.debug('removing {}'.format(fn))
                os.unlink(os.path.join(root, fn))
            for dn in dirs:
                # recursive
                try:
                    logger.debug('recursive del {}'.format(dn))
                    shutil.rmtree(os.path.join(root, dn))
                except Exception:
                    # for now, halt on all.  Override with shutil onerror
                    # callback and ignore_errors.
                    raise
    
    
    class TestChangeMe(unittest.TestCase):
        '''
            TestChangeMe
        '''
        testdatadir = None
        rawdata_dir = None
        testfiles   = None
        output_dir  = output_dir
    
        def __init__(self, *args, **kwargs):
            self.testdatadir = os.path.join(os.path.dirname(
                os.path.abspath(__file__)), testdatadir)
            super(TestChangeMe, self).__init__(*args, **kwargs)
            # check for kwargs
            # this allows test control by instance
            self.testdatadir = kwargs.get('testdatadir', testdatadir)
            self.rawdata_dir = kwargs.get('rawdata_dir', rawdata_dir)
            self.testfiles = kwargs.get('testfiles', testfiles)
            self.output_dir = kwargs.get('output_dir', output_dir)
    
        def setUp(self):
            '''setUp
            pre-test setup called before each test
            '''
            logging.debug('setUp')
            if not os.path.exists(self.testdatadir):
                os.mkdir(self.testdatadir)
            else:
                self.assertTrue(os.path.isdir(self.testdatadir))
            self.assertTrue(os.path.exists(self.testdatadir))
            cleanPath(self.output_dir)
    
        def tearDown(self):
            '''tearDown
            post-test cleanup, if required
            '''
            logging.debug('tearDown')
            if purge_results:
                cleanPath(self.output_dir)
    
        def tupe_as_arg(self, tuple1, tuple2, tuple3, tuple4):
            '''test_something_0
                auto-run tests sorted by ascending alpha
            '''
            # for testing, recreate strings and lens
            string1 = 'string number 1'
            len_s1 = len(string1)
            string2 = 'string number 2'
            len_s2 = len(string2)
            # run the same tests...
            # should test as type = string
            self.assertTrue(type(tuple1) == str)
            self.assertFalse(type(tuple1) == tuple)
            self.assertEqual(len_s1, len_s2, len(tuple1))
            self.assertEqual(len(tuple2), 1)
            # this will fail
            # self.assertEqual(len(tuple4), 1)
            self.assertEqual(len(tuple3), 2)
            self.assertTrue(type(string1) == str)
            self.assertTrue(type(string2) == str)
            self.assertTrue(string1 == tuple1)
            # should test as type == tuple
            self.assertTrue(type(tuple2) == tuple)
            self.assertTrue(type(tuple4) == tuple)
            self.assertFalse(type(tuple1) == type(tuple2))
            self.assertFalse(type(tuple1) == type(tuple4))
            # this will fail
            # self.assertFalse(len(tuple4) == len(tuple1))
            self.assertFalse(len(tuple2) == len(tuple1))
    
        def default_test(self):
            '''testFileDetection
            Tests all data files for type and compares the results to the current
            stored results.
            '''
            # test 1
            __import__('pudb').set_trace()
            string1 = 'string number 1'
            len_s1 = len(string1)
            string2 = 'string number 2'
            len_s2 = len(string2)
            tuple1 = (string1)
            tuple2 = (string1,)
            tuple3 = (string1, string2)
            tuple4 = tuple(string1,)
            # should test as type = string
            self.assertTrue(type(tuple1) == str)
            self.assertFalse(type(tuple1) == tuple)
            self.assertEqual(len_s1, len_s2, len(tuple1))
            self.assertEqual(len(tuple2), 1)
            # this will fail
            # self.assertEqual(len(tuple4), 1)
            self.assertEqual(len(tuple3), 2)
            self.assertTrue(type(string1) == str)
            self.assertTrue(type(string2) == str)
            self.assertTrue(string1 == tuple1)
            # should test as type == tuple
            self.assertTrue(type(tuple2) == tuple)
            self.assertTrue(type(tuple4) == tuple)
            self.assertFalse(type(tuple1) == type(tuple2))
            self.assertFalse(type(tuple1) == type(tuple4))
            # this will fail
            # self.assertFalse(len(tuple4) == len(tuple1))
            self.assertFalse(len(tuple2) == len(tuple1))
            self.tupe_as_arg(tuple1, tuple2, tuple3, tuple4)
    # stand-alone test execution
    if __name__ == '__main__':
        import nose2
        nose2.main(
            argv=[
                'fake',
                '--log-capture',
                'TestChangeMe.default_test',
            ])
    

    您将注意到(几乎)相同的代码调用tuple(string1,)显示为tuple类型,但长度将与字符串长度相同,并且所有成员都是单个字符。

    这将导致第137行、第147行、第104行和第115行的断言失败,即使它们看起来与通过的断言相同。

    (注意:我在第124行的代码中有一个pudb断点,这是一个很好的调试工具,但是如果愿意,您可以删除它。否则很简单 pip install pudb 使用它。)