繁体   English   中英

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

[英]What's the best practice for handling single-value tuples in Python?

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

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

, 在单个关键字的情况下, for迭代字符串的每个字符,在运行时或其他情况下都不会抛出异常,但对我来说完全没用。

我的问题有两个:

显然,这是库中的一个错误,这是我无法控制的。 我怎样才能最好地解决它?

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

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,)语法单独创建元组? 这意味着任何有可能返回单值元组的函数都必须这样做,这看起来很笨拙且重复。

这个问题有一些优雅的通用解决方案吗?

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

keywords = library.get_keywords()
if not isinstance(keywords, tuple):
    keywords = (keywords,) # Note the comma
for keyword in keywords:
    do_your_thang(keyword)

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

至于你的第二个问题,任何变量都可以通过在它旁边放置一个,变成一个单值元组:

>>> 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')

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

您的tuple_maker没有做您认为的那样。 tuple maker的等效定义是

def tuple_maker(input):
    return input

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

Python 中的元组由逗号而不是括号定义。 因此(1,2)是一个包含值12的元组,而(1,)是一个包含单个值1的元组。

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

>>> tuple([1])
(1,)
>>> tuple([1,2])
(1,2)

()与 python 中的元组无关,元组语法使用, () -s 是可选的。

例如:

>>> a=1, 2, 3
>>> type(a)
<class 'tuple'>
>>> a=1,
>>> type(a)
<class 'tuple'>
>>> a=(1)
>>> type(a)
<class 'int'>

我想这是问题的根源。

总是有猴子补丁!

# 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

注意:此代码可能会烧毁您的房子并与您的妻子睡觉。

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

>>> isinstance('a_str', tuple)
False
>>> isinstance(('str1', 'str2', 'str3'), tuple)
True

它绝对有必要返回元组,还是任何可迭代的东西?

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

对于你的第一个问题,你可以检查返回值是否是元组使用

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])

在使用 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即可使用它。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM