Как лучше всего обрабатывать однозначные кортежи в Python? - PullRequest
11 голосов
/ 21 января 2010

Я использую стороннюю библиотечную функцию, которая читает набор ключевых слов из файла и должна возвращать кортеж значений. Он делает это правильно, если есть хотя бы два ключевых слова. Однако в случае, когда есть только одно ключевое слово, он возвращает необработанную строку, а не кортеж размера один. Это особенно пагубно, потому что когда я пытаюсь сделать что-то вроде

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,)? Это подразумевает, что любая функция, которая имеет возможность возвращать однозначный кортеж, должна делать это, что кажется хакерским и повторяющимся.

Есть ли какое-то элегантное общее решение этой проблемы?

Ответы [ 8 ]

15 голосов
/ 21 января 2010

Вам нужно как-то проверить тип, если это строка или кортеж. Я бы сделал это так:

keywords = library.get_keywords()
if not isinstance(keywords, tuple):
    keywords = (keywords,) # Note the comma
for keyword in keywords:
    do_your_thang(keyword)
8 голосов
/ 21 января 2010

Для вашей первой проблемы я не совсем уверен, что это лучший ответ, но я думаю, что вам нужно проверить, является ли возвращаемое значение строкой или кортежем, и действовать соответственно.

Что касается вашей второй проблемы, любую переменную можно превратить в однозначный кортеж, поместив рядом с ней ,:

>>> 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 или любую другую форму логики, которая должна проверять тип объекта во время выполнения. Но для этой проблемы я не вижу пути ее решения.

2 голосов
/ 22 января 2010

Всегда есть обезьяна!

# 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

ПРИМЕЧАНИЕ: Этот код может сжечь ваш дом и спать с вашей женой.

2 голосов
/ 21 января 2010

Ваш 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)
1 голос
/ 21 января 2010

Абсолютно необходимо, чтобы он возвращал кортежи, или он будет повторяться?

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
1 голос
/ 21 января 2010

Вместо проверки длины 1 я бы использовал встроенный isinstance.

>>> isinstance('a_str', tuple)
False
>>> isinstance(('str1', 'str2', 'str3'), tuple)
True
0 голосов
/ 04 мая 2018

При использовании метода конструктора 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',
        ])

Вы заметите, что (почти) идентичный код, вызывающий кортеж (string1,), показан как тип кортежа, но длина будет равна длине строки, и все члены будут одиночными символами.

Это приведет к тому, что утверждения в строках # 137, # 147, # 104 и # 115 потерпят неудачу, даже если они кажутся идентичными пропущенным.

(примечание: у меня есть точка останова PUDB в коде в строке # 124, это отличный инструмент отладки, но вы можете удалить ее, если хотите. В противном случае просто используйте pip install pudb, чтобы использовать ее.)

0 голосов
/ 21 января 2010

для вашей первой проблемы вы можете проверить, является ли возвращаемое значение кортежем, используя

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...