簡體   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