17

ファイルから一連のキーワードを読み取り、値のタプルを返すことになっているサードパーティのライブラリ関数を使用しています。少なくとも 2 つのキーワードがある限り、これは正しく行われます。ただし、キーワードが 1 つしかない場合は、サイズが 1 のタプルではなく、生の文字列を返します。次のようなことをしようとすると、これは特に有害です

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

、単一のキーワードの場合for、文字列の各文字を連続して繰り返します。実行時またはその他の場合に例外はスローされませんが、それでも私にはまったく役に立ちません。

私の質問は 2 つあります。

明らかに、これはライブラリのバグであり、私の手に負えません。どうすればそれを回避できますか?

次に、一般に、タプルを返す関数を作成している場合、1 つの要素を持つタプルが正しく生成されるようにするためのベスト プラクティスは何ですか? たとえば、私が持っている場合

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,)サイズが 1 かどうかを明示的に確認し、構文を使用してタプルを個別に作成する必要がありますか? これは、単一値のタプルを返す可能性のある関数はすべてこれを行う必要があることを意味しますが、これはハックで反復的です。

この問題に対するエレガントな一般的な解決策はありますか?

4

9 に答える 9

22

文字列またはタプルの場合は、何らかの形で型をテストする必要があります。私は次のようにします:

keywords = library.get_keywords()
if not isinstance(keywords, tuple):
    keywords = (keywords,) # Note the comma
for keyword in keywords:
    do_your_thang(keyword)
于 2010-01-21T18:37:04.137 に答える
8

最初の問題については、これが最良の答えであるかどうかはよくわかりませんが、返された値が文字列かタプルかを自分で確認し、それに応じて行動する必要があると思います.

,2番目の問題については、変数の隣に配置することで、変数を単一の値のタプルに変えることができます。

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

この 2 つのアイデアをまとめると、次のようになります。

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

注: 私見では、実行時にオブジェクトのタイプをチェックする必要がある isinstance やその他の形式のロジックを使用することは一般的に悪い考えです。しかし、この問題については、私はそれを回避する方法がわかりません。

于 2010-01-21T18:33:15.900 に答える
3

あなたはあなた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,)は単一の値を含むタプルです1

他の人が指摘しているように、値をタプルに変換するには、を使用しますtuple

>>> tuple([1])
(1,)
>>> tuple([1,2])
(1,2)
于 2010-01-21T18:42:25.800 に答える
2

常にモンキーパッチがあります!

# 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

注:このコードはあなたの家を焼き尽くし、あなたの妻と一緒に寝る可能性があります。

于 2010-01-21T22:03:23.743 に答える
1

タプルを返すことは絶対に必要ですか、それともイテラブルはそうしますか?

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
于 2010-01-21T19:12:43.663 に答える
1

長さ 1 をチェックする代わりに、組み込みの isinstance を使用します。

>>> isinstance('a_str', tuple)
False
>>> isinstance(('str1', 'str2', 'str3'), tuple)
True
于 2010-01-21T18:31:50.990 に答える
0

最初の問題では、戻り値がタプルであるかどうかを確認できます

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

私が使用したい2番目のものtuple([1])。好みの問題だと思います。たとえば、おそらくラッパーを書くこともできますdef tuple1(s): return tuple([s])

于 2010-01-21T18:29:56.680 に答える
0

単一文字列のタプルを作成するためにデフォルトの型定義の代わりに 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使用するだけです。)

于 2018-05-04T14:42:48.690 に答える