在 Python 中重构 For 循环

Refactoring For-Loops in Python

我目前正在开发一个 iTunes 数据程序,该程序不断循环浏览用户的图书馆以获取有关某个图书馆的统计信息。 returns 我有一些这样的代码片段:

def numArtist(self):
    num = 0
    for song in self.allSongs:
        tempList = []
        if song.artist not in tempList:
            tempList.append(song.artist)
            num += 1
    return num

def getAlbumNames(self):
    albums = []
    for song in self.allSongs:
        if song.album not in albums:
            albums.append(song.album)
    return albums

重复主要 for 循环体的地方:

  for song in self.allSongs: # same for-loop condition
       # different for-loop body 

有没有办法重构像这样的方法,我有相同的 for 循环条件但主体定义不同?

我有很多方法都使用相同的 for 循环,所以我想找到一种方法来降低代码的复杂性和冗余。


仅供参考,所有 Song 对象都具有我用来获取数据的属性 - 艺术家、专辑(名称)、流派等。

如果您的 allSongs 列表的内容是不可变的 - 我怀疑它们是 - 您可以将您的 list 转换为 set 然后返回 list s 再次 - 或使用集合理解 - 摆脱重复。那么你的函数可以像这样大大简化:

def numArtist(self):
    return len({song.artist for sing in self.allSongs})

def getAlbumNames(self):
    return list({song.album for song in self.allSongs})

如果您不确定 song 对象是否可变,请尝试一下。如果它们是可变对象,你会得到一个异常:

TypeError: unhashable type: ...

您可以对这两个片段使用集合推导,如果这算作有效 "For-Loop refactoring":

artist_count = len({song.artist for song in self.allSongs})

album_names = set({song.album for song in self.allSongs})

通用版本使用 getattr

get_values = lambda objs, attr: {getattr(obj, attr) for obj in objs

attributes = 'artist', 'album'
values = [get_values(self.allSongs, name) for name in attributes]

artists, albums = values
artist_count = len(artists)

通用版本使用 lambda

get_artist = lambda song: song.artist
get_album = lambda song: song.album

getters = get_artist, get_album

values = [
    {func(song) for song in self.allSongs}
    for getter in getters
]

artists, albums = values
artist_count = len(artists)

通用版本使用 property

# If `song` is an instance of the `Song` class and both `artist` and 
# `album` are properties defined on the class, it's also possible to
# directly use the property getter (`property.fget`) to avoid defining
# the lambdas manually:

get_artist = Song.artist.fget
get_album = Song.album.fget

... # <same as above>

使用set comprehensionslen来简化它们:

def numArtist(self):
    return len({song.artist for song in self.allSongs})

def getAlbumNames(self):
    return {song.album for song in self.allSongs}

为了使其更通用,您可以编写一个方法,该方法采用 lambda 并使用它从每首歌曲中过滤出 属性:

def uniqueProps(self, fxn):
    return {fxn(song) for song in self.allSongs}

def getAlbumNames(self):
    return self.uniqueProps(lambda song: song.album)

您可以尝试创建生成歌曲属性值的生成器。让我举个例子:

def gen_attr(songs, attr_name):
  for song in songs:
    yield getattr(song, attr_name)

class Song(object):
  def __init__(self, name, artist):
    self.name = name
    self.artist = artist

class Album(object):
  def __init__(self, songs_list):
    self.songs_list = songs_list
  def allSongs(self):
    return self.songs_list

s = Song('Ahoy', 'Pirate')
s1 = Song('Bye', 'My Son')
s2 = Song('Ahoy', 'Captain')

a = Album([s, s1])

现在如果你想得到所有的歌曲名称,你可以使用:

song_names = list(gen_attr(a.allSongs(), 'name'))
print(song_names) # ['Ahoy', 'Bye', 'Ahoy'] 

对于不重复的歌曲名称,您可以使用:

song_names = list(set(gen_attr(a.allSongs(), 'name')))
print(song_names) # ['Ahoy', 'Bye'] 

要计算不重复的艺术家姓名,您可以使用:

artists = len(set(gen_attr(a.allSongs(), 'artist')))

要创建艺术家列表,只需执行以下操作:

artists = list(gen_attr(a.allSongs(), 'artist'))
print(artists) # ['Pirate', 'My Son', 'Captain']