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 variablematch
(which is eitherNone
or are.Match
object) - Uses this
match
named expression in place (eitherNone
or aMatch
) to filter out non matching elements - And re-uses
match
in the mapped value by extracting the first group (match.group(1)
).
精彩评论