在 Python 中处理单值元组的最佳实践是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2111759/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What's the best practice for handling single-value tuples in Python?
提问by ire_and_curses
I am using a 3rd party library function which reads a set of keywords from a file, and is supposed to return a tuple of values. It does this correctly as long as there are at least two keywords. However, in the case where there is only one keyword, it returns a raw string, not a tuple of size one. This is particularly pernicious because when I try to do something like
我正在使用第 3 方库函数,它从文件中读取一组关键字,并且应该返回一组值。只要至少有两个关键字,它就会正确执行此操作。但是,在只有一个关键字的情况下,它返回一个原始字符串,而不是大小为 1 的元组。这特别有害,因为当我尝试做类似的事情时
for keyword in library.get_keywords():
# Do something with keyword
, in the case of the single keyword, the for
iterates over each character of the string in succession, which throws no exception, at run-time or otherwise, but is nevertheless completely useless to me.
, 在单个关键字的情况下,for
连续迭代字符串的每个字符, 在运行时或其他情况下都不会抛出异常, 但对我来说完全没用.
My question is two-fold:
我的问题有两个:
Clearly this is a bug in the library, which is out of my control. How can I best work around it?
显然,这是库中的一个错误,这是我无法控制的。我怎样才能最好地解决它?
Secondly, in general, if I am writing a function that returns a tuple, what is the best practice for ensuring tuples with one element are correctly generated? For example, if I have
其次,一般来说,如果我正在编写一个返回元组的函数,那么确保正确生成具有一个元素的元组的最佳实践是什么?例如,如果我有
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
I get
我得到
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
What is the best way to modify the function my_tuple
to actually return a tuple when there is only a single element? Do I explicitly need to check whether the size is 1, and create the tuple seperately, using the (value,)
syntax? This implies that any function that has the possibility of returning a single-valued tuple must do this, which seems hacky and repetitive.
my_tuple
当只有一个元素时,修改函数以实际返回元组的最佳方法是什么?我是否明确需要检查大小是否为 1,并使用(value,)
语法单独创建元组?这意味着任何有可能返回单值元组的函数都必须这样做,这看起来很笨拙且重复。
Is there some elegant general solution to this problem?
这个问题有一些优雅的通用解决方案吗?
回答by Lennart Regebro
You need to somehow test for the type, if it's a string or a tuple. I'd do it like this:
您需要以某种方式测试类型,如果它是字符串或元组。我会这样做:
keywords = library.get_keywords()
if not isinstance(keywords, tuple):
keywords = (keywords,) # Note the comma
for keyword in keywords:
do_your_thang(keyword)
回答by MAK
For your first problem, I'm not really sure if this is the best answer, but I think you need to check yourself whether the returned value is a string or tuple and act accordingly.
对于您的第一个问题,我不确定这是否是最佳答案,但我认为您需要检查自己返回的值是字符串还是元组并采取相应措施。
As for your second problem, any variable can be turned into a single valued tuple by placing a ,
next to it:
至于你的第二个问题,任何变量都可以通过在它,
旁边放置一个来变成一个单值元组:
>>> x='abc'
>>> x
'abc'
>>> tpl=x,
>>> tpl
('abc',)
Putting these two ideas together:
把这两个想法放在一起:
>>> def make_tuple(k):
... if isinstance(k,tuple):
... return k
... else:
... return k,
...
>>> make_tuple('xyz')
('xyz',)
>>> make_tuple(('abc','xyz'))
('abc', 'xyz')
Note: IMHO it is generally a bad idea to use isinstance, or any other form of logic that needs to check the type of an object at runtime. But for this problem I don't see any way around it.
注意:恕我直言,使用 isinstance 或任何其他需要在运行时检查对象类型的逻辑形式通常是一个坏主意。但是对于这个问题,我看不到任何解决方法。
回答by me_and
Your tuple_maker
doesn't do what you think it does. An equivalent definition of tuple maker
to yours is
你tuple_maker
没有做你认为它做的事情。tuple maker
与您的等效定义是
def tuple_maker(input):
return input
What you're seeing is that tuple_maker("a string")
returns a string, while tuple_maker(["str1","str2","str3"])
returns a list of strings; neither return a tuple!
你看到的是tuple_maker("a string")
返回一个字符串,而tuple_maker(["str1","str2","str3"])
返回一个字符串列表;既不返回元组!
Tuples in Python are defined by the presence of commas, not brackets. Thus (1,2)
is a tuple containing the values 1
and 2
, while (1,)
is a tuple containing the single value 1
.
Python 中的元组由逗号而不是括号定义。因此(1,2)
是一个包含值1
和的元组2
,而(1,)
是一个包含单个值的元组1
。
To convert a value to a tuple, as others have pointed out, use tuple
.
正如其他人指出的那样,要将值转换为元组,请使用tuple
.
>>> tuple([1])
(1,)
>>> tuple([1,2])
(1,2)
回答by Will McCutchen
There's always monkeypatching!
总是有猴子补丁!
# 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
NOTE:This code might burn down your house and sleep with your wife.
注意:此代码可能会烧毁您的房子并与您的妻子睡觉。
回答by Epcylon
Is it absolutely necessary that it returns tuples, or will any iterable do?
它绝对有必要返回元组,还是任何可迭代的东西?
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
回答by Josh Wright
Rather than checking for a length of 1, I'd use the isinstance built-in instead.
我不会检查长度为 1,而是使用内置的 isinstance。
>>> isinstance('a_str', tuple)
False
>>> isinstance(('str1', 'str2', 'str3'), tuple)
True
回答by Zoltan K.
The ()
have nothing to do with tuples in python, the tuple syntax uses ,
. The ()
-s are optional.
在()
什么都没有做,在蟒蛇元组,元组语法使用,
。该()
-s是可选的。
E.g.:
例如:
>>> a=1, 2, 3
>>> type(a)
<class 'tuple'>
>>> a=1,
>>> type(a)
<class 'tuple'>
>>> a=(1)
>>> type(a)
<class 'int'>
I guess this is the root of the problem.
我想这是问题的根源。
回答by Anycorn
for your first problem you could check if the return value is tuple using
对于你的第一个问题,你可以检查返回值是否是元组使用
type(r) is tuple
#alternative
isinstance(r, tuple)
# one-liner
def as_tuple(r): return [ tuple([r]), r ][type(r) is tuple]
the second thing i like to use tuple([1])
. think it is a matter of taste. could probably also write a wrapper, for example def tuple1(s): return tuple([s])
我喜欢使用的第二件事tuple([1])
。认为这是一个品味问题。也可以写一个包装器,例如def tuple1(s): return tuple([s])
回答by SkyLeach
There is an important thing to watch out for when using the tuple() constructor method instead of the default type definition for creating your single-string tuples. Here is a Nose2/Unittest script you can use to play with the problem:
在使用 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',
])
You will notice that the (nearly) identical code calling tuple(string1,) shows as type tuple, but the length will be the same as the string length and all members will be single characters.
您会注意到调用 tuple(string1,) 的(几乎)相同的代码显示为类型 tuple,但长度将与字符串长度相同,并且所有成员都将是单个字符。
This will cause the assertions on lines #137, #147, #104 and #115 to fail, even though they are seemingly identical to the ones that pass.
这将导致行 #137、#147、#104 和 #115 上的断言失败,即使它们看起来与通过的断言相同。
(note: I have a PUDB breakpoint in the code at line #124, it's an excellent debug tool, but you can remove it if you prefer. Otherwise simply pip install pudb
to use it.)
(注意:我在第 124 行的代码中有一个 PUDB 断点,它是一个出色的调试工具,但如果您愿意,可以将其删除。否则只需pip install pudb
使用它。)