Skip to content

Commit

Permalink
str2list utility for commandline parsing of comma separated lists (#5358
Browse files Browse the repository at this point in the history
)

This adds a utility function str2list to convert a string to a list.
Useful with argparse commandline arguments:
```
        parser.add_argument("--blocks", default=[1,2,3], type=str2list)
        ...
        python mycode.py --blocks=1,2,2,4
```
Unit tests added.

It also includes a small fix for str2bool to accept input as bool (and
return it right away).

### Types of changes
<!--- Put an `x` in all the boxes that apply, and remove the not
applicable items -->
- [x] Non-breaking change (fix or new feature that would not break
existing functionality).
- [ ] Breaking change (fix or new feature that would cause existing
functionality to change).
- [x] New tests added to cover the changes.
- [ x Integration tests passed locally by running `./runtests.sh -f -u
--net --coverage`.
- [x]Quick tests passed locally by running `./runtests.sh --quick
--unittests --disttests`.
- [x] In-line docstrings updated.
- [ ] Documentation updated, tested `make html` command in the `docs/`
folder.

Signed-off-by: myron <[email protected]>
  • Loading branch information
myron authored and wyli committed Oct 19, 2022
1 parent 652511f commit 10ab34a
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 5 deletions.
1 change: 1 addition & 0 deletions monai/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
set_determinism,
star_zip_with,
str2bool,
str2list,
zip_with,
)
from .module import (
Expand Down
47 changes: 44 additions & 3 deletions monai/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"MAX_SEED",
"copy_to_device",
"str2bool",
"str2list",
"MONAIEnvVars",
"ImageMetaKey",
"is_module_ver_at_least",
Expand Down Expand Up @@ -363,21 +364,29 @@ def copy_to_device(
return obj


def str2bool(value: str, default: bool = False, raise_exc: bool = True) -> bool:
def str2bool(value: Union[str, bool], default: bool = False, raise_exc: bool = True) -> bool:
"""
Convert a string to a boolean. Case insensitive.
True: yes, true, t, y, 1. False: no, false, f, n, 0.
Args:
value: string to be converted to a boolean.
value: string to be converted to a boolean. If value is a bool already, simply return it.
raise_exc: if value not in tuples of expected true or false inputs,
should we raise an exception? If not, return `None`.
should we raise an exception? If not, return `default`.
Raises
ValueError: value not in tuples of expected true or false inputs and
`raise_exc` is `True`.
Useful with argparse, for example:
parser.add_argument("--convert", default=False, type=str2bool)
python mycode.py --convert=True
"""

if isinstance(value, bool):
return value

true_set = ("yes", "true", "t", "y", "1")
false_set = ("no", "false", "f", "n", "0")

if isinstance(value, str):
value = value.lower()
if value in true_set:
Expand All @@ -390,6 +399,38 @@ def str2bool(value: str, default: bool = False, raise_exc: bool = True) -> bool:
return default


def str2list(value: Optional[Union[str, list]], raise_exc: bool = True) -> Optional[list]:
"""
Convert a string to a list. Useful with argparse commandline arguments:
parser.add_argument("--blocks", default=[1,2,3], type=str2list)
python mycode.py --blocks=1,2,2,4
Args:
value: string (comma separated) to be converted to a list
raise_exc: if not possible to convert to a list, raise an exception
Raises
ValueError: value not a string or list or not possible to convert
"""

if value is None:
return None
elif isinstance(value, list):
return value
elif isinstance(value, str):
v = value.split(",")
for i in range(len(v)):
try:
a = literal_eval(v[i].strip()) # attempt to convert
v[i] = a
except Exception:
pass
return v
elif raise_exc:
raise ValueError(f'Unable to convert "{value}", expected a comma-separated str, e.g. 1,2,3')

return None


class MONAIEnvVars:
"""
Environment variables used by MONAI.
Expand Down
4 changes: 2 additions & 2 deletions tests/test_str2bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

class TestStr2Bool(unittest.TestCase):
def test_str_2_bool(self):
for i in ("yes", "true", "t", "y", "1"):
for i in ("yes", "true", "t", "y", "1", True):
self.assertTrue(str2bool(i))
for i in ("no", "false", "f", "n", "0"):
for i in ("no", "false", "f", "n", "0", False):
self.assertFalse(str2bool(i))
for bad_value in ("test", 0, 1, 2, None):
self.assertFalse(str2bool(bad_value, default=False, raise_exc=False))
Expand Down
30 changes: 30 additions & 0 deletions tests/test_str2list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright (c) MONAI Consortium
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest

from monai.utils.misc import str2list


class TestStr2List(unittest.TestCase):
def test_str_2_list(self):
for i in ("1,2,3", "1, 2, 3", "1,2e-0,3.0", [1, 2, 3]):
self.assertEqual(str2list(i), [1, 2, 3])
for i in ("1,2,3", "1,2,3,4.3", [1, 2, 3, 4.001]):
self.assertNotEqual(str2list(i), [1, 2, 3, 4])
for bad_value in ((1, 3), int):
self.assertIsNone(str2list(bad_value, raise_exc=False))
with self.assertRaises(ValueError):
self.assertIsNone(str2list(bad_value))


if __name__ == "__main__":
unittest.main()

0 comments on commit 10ab34a

Please sign in to comment.