迭代Python中对变量的引用

时间:2014-11-21 22:05:21

标签: python regex foreach

我有一个名为Song的对象,定义为:

class Song(object):
    def __init__(self):
        self.title = None
        self.songauthor = None
        self.textauthor = None
        self.categories = None

在这个类中,我有一个解析该对象的运行时属性的方法,“元数据”,它基本上只是一个带有一些格式化文本的文本文件,我用正则表达式解析。在这个过程中,我提出了以下代码,我非常肯定可以将其简化为循环。

re_title = re.compile("^title:(.*)$", re.MULTILINE)
re_textauthor = re.compile("^textauthor:(.*)$", re.MULTILINE)
re_songauthor = re.compile("^songauthor:(.*)$", re.MULTILINE)
re_categories = re.compile("^categories:(.*)$", re.MULTILINE)

#
# it must be possible to simplify the below code to a loop...
#
tmp = re_title.findall(self.metadata)
self.title = tmp[0] if len(tmp) > 0 else None

tmp = re_textauthor.findall(self.metadata)
self.textauthor = tmp[0] if len(tmp) > 0 else None

tmp = re_songauthor.findall(self.metadata)
self.songauthor = tmp[0] if len(tmp) > 0 else None

tmp = re_categories.findall(self.metadata)
self.categories = tmp[0] if len(tmp) > 0 else None

我猜这可以通过在数据类型(可能是元组)中封装对属性的引用(例如self.title)和相应的正则表达式(re_title)来完成,然后迭代这些数据类型的列表

我尝试过使用元组:

for x in ((self.title, re_title),
      (self.textauthor, re_textauthor),
      (self.songauthor, re_songauthor),
      (self.categories, re_categories)):
    data = x[1].findall(self.metadata)
    x[0] = data[0] if len(data) > 0 else None

由于我无法在运行时修改元组,因此失败了。任何人都可以提出如何解决这个问题的建议吗?

4 个答案:

答案 0 :(得分:3)

您的代码存在两个问题。

最重要的一点是,x[0]不是对self.title的引用,而是 self.title的值的引用。换句话说,您只是将现有标题复制到元组中,然后用另一个标题替换元组中的标题,这对现有标题没有影响。

较小的一个是你不能替换元组中的元素。你可以通过使用列表而不是元组来解决这个问题,但是你仍然会遇到大问题。

那么,如何在Python中创建对变量的引用?你不能。你需要考虑一种重组事物的方法。例如,您可以通过名称而不是引用来访问这些内容。而不是四个单独的变量,将四个变量的字典存储在一个字典中:

res = {
    'title': re.compile("^title:(.*)$", re.MULTILINE),
    'textauthor': re.compile("^textauthor:(.*)$", re.MULTILINE)
    'songauthor': re.compile("^songauthor:(.*)$", re.MULTILINE)
    'categories': re.compile("^categories:(.*)$", re.MULTILINE)
}

class Song(object):
    def __init__(self):
        self.properties = {}

    def parsify(self, text):
        for thing in ('title', 'textauthor', 'songauthor', 'categories'):
            data = res[thing].findall(self.metadata)
            self.properties[thing] = data[0] if len(data) > 0 else None

您也可以在那里使用for thing in res:,因为这会迭代所有键(按任意顺序,但您可能不关心订单)。

如果您确实需要self.title,那么您遇到了一个常见问题。通常,数据(应该由运行时字符串引用)和属性(不应该引用)之间有明确的区别。但有时候,没有。所以你必须以某种方式在它们之间架起桥梁。您可以创建@property的四个return self.properties['title']字段,也可以使用setattr(self, thing, …)代替self.properties[thing],或其他各种可能性。哪一个最好归结为它们是更像数据还是更像属性。

答案 1 :(得分:2)

不是分配给元组,而是直接更新类成员:

all_res = {'title':re_title,
           'textauthor': re_textauthor,
           'songauthor': re_song_author,
           'categories': re_categories}

for k, v in all_res.iteritems():
    tmp = v.findall(self.metadata)
    if tmp:
        setattr(self, k, tmp[0])
    else:
        setattr(self, k, None)

如果您只关心第一场比赛,则不需要使用findall

答案 2 :(得分:1)

abarnert的回答很好地解释了您的代码出了什么问题,但我想提供一个替代解决方案。不要使用循环来分配每个变量,而是尝试从解析的文件中创建不同值的可迭代,然后使用单个解包赋值将它们放入各种变量中。

这是一个使用列表推导的双语句解决方案,由于您需要在findall / if中两次引用else的结果,这有点棘手表达式(因此嵌套的生成器表达式):

vals = [x[0] if len(x) > 0 else None for x in (regex.findall(self.metadata) for regex in
                                               [re_title, re_textauthor,
                                                re_songauthor, re_categories])]
self.title, self.textauthor, self.songauthor, self.categories = vals

你可以在列表理解的第一部分中简化一些事情。首先,您只需测试if x而不是if len(x) > 0。或者,如果您不太习惯使用findall,则可以使用search,然后使用x and x.group(0)代替整个if / else位。如果未找到匹配项,则search方法会返回None,因此and运算符的短路行为将完全符合我们的要求。

答案 3 :(得分:0)

一个例子是使用这样的字典:

things = {}

for x in ((self.title, re_title),
  (self.textauthor, re_textauthor),
  (self.songauthor, re_songauthor),
  (self.categories, re_categories)):
    if len(x[1].findall(self.metadata):
        things[x[0]] = x[1].findall(self.metadata)[1]
    else:
        things[x[0]] = None

这可能是一个可能的解决方案吗?