python - Python如何将特定的字符串提取到多个变量中

标签 python

我试图提取特定行作为文件中的变量。

这是我的test.txt的内容

#first set
Task Identification Number: 210CT1
Task title: Assignment 1
Weight: 25
fullMark: 100
Description: Program and design and complexity running time.

#second set
Task Identification Number: 210CT2
Task title: Assignment 2
Weight: 25
fullMark: 100
Description: Shortest Path Algorithm

#third set
Task Identification Number: 210CT3
Task title: Final Examination
Weight: 50
fullMark: 100
Description: Close Book Examination


这是我的代码

with open(home + '\\Desktop\\PADS Assignment\\test.txt', 'r') as mod:
    for line in mod:
        taskNumber , taskTile , weight, fullMark , desc = line.strip(' ').split(": ") 
        print(taskNumber)
        print(taskTile)
        print(weight)
        print(fullMark)
        print(description)


这是我想要做的:

taskNumber is 210CT1 
taskTitle is Assignment 1
weight is 25
fullMark is 100
desc is Program and design and complexity running time

and loop until the third set 


但输出中发生错误

ValueError: not enough values to unpack (expected 5, got 2)


对SwiftsNamesake的回应

我尝试了您的代码。我仍然遇到错误。

ValueError: too many values to unpack (expected 5)


这是我尝试通过使用您的代码

 from itertools import zip_longest

 def chunks(iterable, n, fillvalue=None):
     args = [iter(iterable)] * n
     return zip_longest(*args, fillvalue=fillvalue)


with open(home + '\\Desktop\\PADS Assignment\\210CT.txt', 'r') as mod:
    for group in chunks(mod.readlines(), 5+2, fillvalue=''):
    # Choose the item after the colon, excluding the extraneous rows
    # that don't have one.
    # You could probably find a more elegant way of achieving the same thing
        l = [item.split(': ')[1].strip() for item in group if ':' in item]
    taskNumber , taskTile , weight, fullMark , desc = l
        print(taskNumber , taskTile , weight, fullMark , desc, sep='|')

最佳答案

如前所述,您需要某种分块。为了有用地分块,我们还需要忽略文件的无关行。我已经在下面的一些不错的Python巫术中实现了这样的功能。

使用namedtuple存储值也可能适合您。 namedtuple是一种非常简单的对象类型,它仅存储许多不同的值-例如,二维空间中的一个点可能是带有x和y字段的namedtuple。这是Python documentation中给出的示例。如果需要,您应该参考该链接以获取有关namedtuple及其用法的更多信息。我已经冒用字段["number", "title", "weight", "fullMark", "desc"]创建Task类的自由。

由于变量是任务的所有属性,因此,为了简洁起见,使用命名元组可能是有意义的。

除此之外,我一直试图坚持您的方法,以冒号分割。我的代码产生输出

================================================================================
number is 210CT1
title is Assignment 1
weight is 25
fullMark is 100
desc is Program and design and complexity running time.
================================================================================
number is 210CT2
title is Assignment 2
weight is 25
fullMark is 100
desc is Shortest Path Algorithm
================================================================================
number is 210CT3
title is Final Examination
weight is 50
fullMark is 100
desc is Close Book Examination


这似乎与您的追求大致相同-我不确定您的输出要求有多严格。但是,为此目的进行修改应该相对容易。

这是我的代码,带有一些解释性注释:

from collections import namedtuple

#defines a simple class 'Task' which stores the given properties of a task
Task = namedtuple("Task", ["number", "title", "weight", "fullMark", "desc"])

#chunk a file (or any iterable) into groups of n (as an iterable of n-tuples)
def n_lines(n, read_file):
    return zip(*[iter(read_file)] * n)

#used to strip out empty lines and lines beginning with #, as those don't appear to contain any information
def line_is_relevant(line):
    return line.strip() and line[0] != '#'

with open("input.txt") as in_file:
    #filters the file for relevant lines, and then chunks into 5 lines
    for task_lines in n_lines(5, filter(line_is_relevant, in_file)):
        #for each line of the task, strip it, split it by the colon and take the second element
        #(ie the remainder of the string after the colon), and build a Task from this
        task = Task(*(line.strip().split(": ")[1] for line in task_lines))
        #just to separate each parsed task
        print("=" * 80)
        #iterate over the field names and values in the task, and print them
        for name, value in task._asdict().items():
            print("{} is {}".format(name, value))


您还可以引用任务的每个字段,如下所示:

            print("The number is {}".format(task.number))


如果不希望使用namedtuple方法,请随时将main for循环的内容替换为

        taskNumber, taskTitle, weight, fullMark, desc = (line.strip().split(": ")[1] for line in task_lines)


然后您的代码将恢复正常。

关于我进行的其他更改的一些注意事项:

filter像在锡罐上所说的那样进行操作,仅迭代符合谓词的行(line_is_relevant(line)True)。

Task实例中的*将迭代器解包,因此每个解析的行都是Task构造函数的一个参数。

表达式(line.strip().split(": ")[1] for line in task_lines)是一个生成器。这是必需的,因为我们使用task_lines一次执行多行,因此对于“块”中的每一行,我们都将其剥离,用冒号分割,然后取第二个元素,即值。

n_lines函数通过将对同一迭代器的n个引用的列表传递给zip函数(documentation)来工作。然后,zip尝试从此列表的每个元素中产生下一个元素,但是由于n个元素中的每个元素都是文件上的迭代器,因此zip产生文件的n行。这一直持续到迭代器耗尽为止。

line_is_relevant函数使用“真实性”的思想。一种更详细的实现方法可能是

def line_is_relevant(line):
    return len(line.strip()) > 0 and line[0] != '#'


但是,在Python中,每个对象都可以在布尔逻辑表达式中隐式使用。这样的表达式中的空字符串("")充当False,非空字符串充当True,因此,如果line.strip()为空,它将方便地充当Falseline_is_relevant因此将为False。如果第一个操作数为假,则and运算符也会短路,这意味着将不对第二个操作数进行求值,因此,方便地引用line[0]不会导致IndexError

好的,这是我对n_lines function的更详细解释的尝试:

首先,zip函数使您可以一次迭代多个“ iterable”。可迭代对象类似于列表或文件,您可以在for循环中进行遍历,因此zip函数可以让您执行以下操作:

>>> for i in zip(["foo", "bar", "baz"], [1, 4, 9]):
...     print(i)
... 
('foo', 1)
('bar', 4)
('baz', 9)


zip函数一次从每个列表返回一个元素的'tuple'。元组基本上是一个列表,除了它是不可变的之外,因此您无法更改它,因为zip并不期望您更改它提供的任何值,而是对其进行处理。除此以外,元组可以像普通列表一样使用。现在,这里有用的技巧是使用“拆包”来分隔元组的每个位,如下所示:

>>> for a, b in zip(["foo", "bar", "baz"], [1, 4, 9]):
...     print("a is {} and b is {}".format(a, b))  
... 
a is foo and b is 1
a is bar and b is 4
a is baz and b is 9


您可能之前已经看过一个更简单的解压缩示例(Python还可让您在此处省略括号()):

>>> a, b = (1, 2)
>>> a
1
>>> b
2


尽管n-lines function不使用它。现在zip也可以使用多个参数-您可以随意压缩三个,四个或任意多个列表(相当多)。

>>> for i in zip([1, 2, 3], [0.5, -2, 9], ["cat", "dog", "apple"], "ABC"):
...     print(i)
... 
(1, 0.5, 'cat', 'A')
(2, -2, 'dog', 'B')
(3, 9, 'apple', 'C')


现在,n_lines函数将*[iter(read_file)] * n传递给zip。这里有几件事要覆盖-我将从第二部分开始。请注意,第一个*的优先级低于其后的所有优先级,因此它等效于*([iter(read_file)] * n)。现在,iter(read_file)的作用是通过调用read_fileiter构造一个迭代器对象。迭代器有点像列表,只是不能像it[0]那样对其编制索引。您所能做的就是“迭代”,就像在for循环中进行遍历一样。然后,它使用此迭代器作为唯一元素来构建长度为1的列表。然后,它将这个列表与n相乘。

在Python中,将*运算符与列表一起使用会将其自身连接为n次。如果您考虑一下,由于+是串联运算符,因此这很有意义。因此,例如

>>> [1, 2, 3] * 3 == [1, 2, 3] + [1, 2, 3] + [1, 2, 3] == [1, 2, 3, 1, 2, 3, 1, 2, 3]
True


顺便说一下,这使用了Python的链式比较运算符-a == b == c等同于a == b and b == c,除了b只需要被评估一次,这在99%的时间内都无关紧要。

无论如何,我们现在知道*运算符将列表复制了n次。它还具有另一个属性-它不构建任何新对象。这可能有点麻烦-

>>> l = [object()] * 3
>>> id(l[0])
139954667810976
>>> id(l[1])
139954667810976
>>> id(l[2])
139954667810976


这里l是三个object-但实际上它们都是同一对象(您可能会认为这是指向同一对象的三个“指针”)。如果要构建更复杂的对象(例如列表)的列表,并执行就位操作(如对它们进行排序),则会影响列表中的所有元素。

>>> l = [ [3, 2, 1] ] * 4
>>> l
[[3, 2, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1]]
>>> l[0].sort()
>>> l
[[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]


所以[iter(read_file)] * n等效于

it = iter(read_file)
l = [it, it, it, it... n times]


现在,第一个*(优先级较低的那个)再次“解包”,但这一次并没有将其分配给变量,而是将其分配给zip的参数。这意味着zip会将列表的每个元素作为一个单独的参数接收,而不只是列表中的一个参数。这是在更简单的情况下如何拆包的示例:

>>> def f(a, b):
...     print(a + b)
... 
>>> f([1, 2]) #doesn't work
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required positional argument: 'b'
>>> f(*[1, 2]) #works just like f(1, 2)
3


所以实际上,我们现在有了

it = iter(read_file)
return zip(it, it, it... n times)


请记住,当您在for循环中“迭代”文件对象时,您会遍历文件的每一行,因此,当zip尝试一次“遍历” n个对象的每一个时,它会从每个对象中绘制一行-但是由于每个对象都是相同的迭代器,因此此行“已消耗”,并且绘制的下一行是文件中的下一行。从它的n个参数中的每个参数进行一轮“迭代”会产生n行,这就是我们想要的。

关于python - Python如何将特定的字符串提取到多个变量中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45774259/

相关文章:

python - 如何在 Python 3.2 或更高版本中使用 'hex' 编码?

python - 使用 BeautifulSoup 删除所有内联样式

python - 如何使用python使用firestore字段增量?

python - Alexa 网站排名 API

python - 使用 pyparsing 跳到文本中的第一个可能性

python - 使用 python3 时无法绘制系列类型

python - 获取 QComboBox 的所有项目 - PyQt4 (Python)

python - 如何从包含许多已排序列表的列表中获取排序列表?

python - 如何在Python中编写numpy矩阵的函数

python - 如何将带有 concat 的行附加到 Pandas DataFrame