Фильтрация и преобразование списка

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

libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
versions = []
regex = re.compile('libIce.so\.([0-9]+\.[0-9]+\.[0-9]+)')
for l in libs:
    m = regex.match(l)
    if m:
        versions.append(m.group(1))

Это приводит к следующему списку:

['3.3.1', '3.2.0']

Тем не менее, я чувствую, что цикл не очень "стиль Python" и чувствую, что должно быть возможно заменить цикл "for" выше на некоторый интеллектуальный однострочный. Предложения?

Ответ 1

Как насчет понимания списка?

In [5]: versions = [m.group(1) for m in [regex.match(lib) for lib in libs] if m] 
In [6]: versions
Out[6]: ['3.3.1', '3.2.0']

Ответ 2

Еще один однострочный экран, чтобы показать другие способы (я также немного очистил регулярное выражение):

regex = re.compile(r'^libIce\.so\.([0-9]+\.[0-9]+\.[0-9]+)$')
sum(map(regex.findall, libs), [])

Но обратите внимание, что ваша оригинальная версия более читаема, чем все предложения. Стоит ли меняться?

Ответ 3

Вы можете сделать это:

versions = [m.group(1) for m in [regex.match(l) for l in libs] if m]

Я не думаю, что это очень читаемо, хотя...

Возможно, это более четкое выполнение в два этапа:

matches = [regex.match(l) for l in line]
versions = [m.group(1) for m in matches if m]

Ответ 4

Нет ничего, что не является питоническим в использовании стандарта для цикла. Однако вы можете использовать функцию map() для создания нового списка на основе результатов от функции, выполняемой против каждого элемента в списке.

Ответ 5

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

>>> libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
>>> libs
['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
>>> for i in libs:
...   print i.split("so.")
...
['libIce.', '33']
['libIce.', '3.3.1']
['libIce.', '32']
['libIce.', '3.2.0']
>>> for i in libs:
...   print i.split("so.")[-1]
...
33
3.3.1
32
3.2.0
>>>

Сделайте еще одну проверку, чтобы получить те, у которых есть "точки".

Ответ 6

Как насчет этого:

import re

def matches(regexp, list):
    'Regexp, [str] -> Iterable(Match or None)'
    return (regexp.match(s) for s in list)

libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
regexp = re.compile('libIce.so\.([0-9]+\.[0-9]+\.[0-9]+)')
versions = [m.group(1) for m in matches(regexp, libs) if m is not None]

>>> print versions
['3.3.1', '3.2.0']

Ответ 7

Один из способов, которым я мог подумать, состоял в том, чтобы объединить "карту" и список. Решение выглядит следующим образом:

import re  
libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']  
versions = []  

regex = re.compile('libIce.so\.([0-9]+\.[0-9]+\.[0-9]+)')  

def match(s):  
    m = regex.match(s)  
    if m:  
        return m.group(1)  

versions = [x for x in map(match,libs) if x]  

Ответ 8

Начиная с Python 3.8 и введением выражений присваивания (PEP 572) (:= оператор), можно использовать локальную переменную в пределах понимания списка, чтобы избежать двойного вызова результата сопоставления с регулярным выражением:

# libs = ['libIce.so.33', 'libIce.so.3.3.1', 'libIce.so.32', 'libIce.so.3.2.0']
# pattern = re.compile(r'libIce.so\.([0-9]+\.[0-9]+\.[0-9]+)')
[match.group(1) for lib in libs if (match := pattern.match(lib))]
# ['3.3.1', '3.2.0']

Это:

  • Называет оценку pattern.match(lib) как match переменной (которое является None или объектом re.Match)
  • Использует это match именованным выражением на месте (None или Match), чтобы отфильтровать несоответствующие элементы
  • И повторно использует match в сопоставленном значении, извлекая первую группу (match.group(1)).