开发者

List filtering and transformation

I have a list of library filenames that I need to filter against regular expression and then extract version number from those that match. This is the obvious way to do that:

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))

That produces the following list:

['3.3.1', '3.2.0']

Yet I feel that loop is not very 'Python style' and feel it should be possible to replace 'for' loop above with some smart one-liner. Sug开发者_运维知识库gestions?


How about a list comprehension?

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']


One more one-liner just to show other ways (I've also cleaned regexp a bit):

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

But note, that your original version is more readable than all suggestions. Is it worth to change?


You could do this:

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

I don't think it's very readable, though...

Maybe it's clearer done in two steps:

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


There's nothing that isn't pythonic about using a standard for loop. However, you can use the map() function to generate a new list based on the results from a function run against each item in the list.


you don't really need to bother with regex for your simple case

>>> 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
>>>

Do further checking to get those with "dots".


How about this one:

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']


One way I could think of was to combine 'map' and list comprehension.
The solution looks as below:

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]  


Starting Python 3.8, and the introduction of assignment expressions (PEP 572) (:= operator), it's possible to use a local variable within a list comprehension in order to avoid calling twice the result of the regex matching:

# 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']

This:

  • Names the evaluation of pattern.match(lib) as a variable match (which is either None or a re.Match object)
  • Uses this match named expression in place (either None or a Match) to filter out non matching elements
  • And re-uses match in the mapped value by extracting the first group (match.group(1)).
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜