Phân tích các giá trị boolean với argparse


615

Tôi muốn sử dụng argparse để phân tích các đối số dòng lệnh boolean được viết là "--foo True" hoặc "--foo Sai". Ví dụ:

my_program --my_boolean_flag False

Tuy nhiên, mã kiểm tra sau không thực hiện được những gì tôi muốn:

import argparse
parser = argparse.ArgumentParser(description="My parser")
parser.add_argument("--my_bool", type=bool)
cmd_line = ["--my_bool", "False"]
parsed_args = parser.parse(cmd_line)

Đáng buồn thay, parsed_args.my_boolđánh giá để True. Đây là trường hợp ngay cả khi tôi thay đổi cmd_lineđược ["--my_bool", ""], mà là ngạc nhiên, vì bool("")evalutates tới False.

Làm thế nào tôi có thể nhận được argparse để phân tích "False", "F"và thấp hơn trường hợp của họ biến thể được False?


40
Dưới đây là cách diễn giải một câu trả lời của câu trả lời @ mgilson parser.add_argument('--feature', dest='feature', default=False, action='store_true') . Giải pháp này sẽ đảm bảo bạn luôn có được một boolloại có giá trị Truehoặc False. (Giải pháp này có một ràng buộc: tùy chọn của bạn phải có giá trị mặc định.)
Trevor Boyd Smith

7
Dưới đây là cách diễn giải một câu trả lời của @ Maximparser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Khi tùy chọn được sử dụng, giải pháp này sẽ đảm bảo boolloại có giá trị Truehoặc False. Khi tùy chọn không được sử dụng, bạn sẽ nhận được None. ( distutils.util.strtobool(x)là từ một câu hỏi stackoverflow khác )
Trevor Boyd Smith

8
làm thế nào về một cái gì đó nhưparser.add_argument('--my_bool', action='store_true', default=False)
AruniRC

Câu trả lời:


276

Một giải pháp khác sử dụng các đề xuất trước đó, nhưng với lỗi phân tích "chính xác" từ argparse:

def str2bool(v):
    if isinstance(v, bool):
       return v
    if v.lower() in ('yes', 'true', 't', 'y', '1'):
        return True
    elif v.lower() in ('no', 'false', 'f', 'n', '0'):
        return False
    else:
        raise argparse.ArgumentTypeError('Boolean value expected.')

Điều này rất hữu ích để thực hiện chuyển đổi với các giá trị mặc định; ví dụ

parser.add_argument("--nice", type=str2bool, nargs='?',
                        const=True, default=False,
                        help="Activate nice mode.")

cho phép tôi sử dụng:

script --nice
script --nice <bool>

và vẫn sử dụng một giá trị mặc định (cụ thể cho cài đặt người dùng). Một nhược điểm (liên quan gián tiếp) với cách tiếp cận đó là các 'nargs' có thể bắt được một đối số vị trí - xem câu hỏi liên quan nàybáo cáo lỗi này .


4
nargs = '?' có nghĩa là không hoặc một đối số. docs.python.org/3/l Library /argparse.html
Maxim

1
Tôi thích điều này, nhưng tương đương với mặc định của tôi = NICE đang gây ra lỗi cho tôi, vì vậy tôi phải làm điều gì đó khác.
Michael Mathews

2
@MarcelloRomani str2bool không phải là một loại theo nghĩa Python, nó là hàm được định nghĩa ở trên, bạn cần đưa nó vào đâu đó.
Tối đa

4
mã của str2bool(v)có thể được thay thế bằng bool(distutils.util.strtobool(v)). Nguồn: stackoverflow.com/a/18472142/2436175
Antonio

4
Có lẽ điều đáng nói là với cách này, bạn không thể kiểm tra xem đối số có được đặt bằng if args.nice:beacuse hay không nếu đối số được đặt thành Sai, nó sẽ không bao giờ vượt qua điều kiện. Nếu điều này đúng thì có lẽ tốt hơn là trả về danh sách từ str2boolhàm và đặt danh sách làm consttham số, như thế này [True], [False]. Sửa lỗi cho tôi nếu tôi sai
NutCracker

888

Tôi nghĩ rằng một cách kinh điển hơn để làm điều này là thông qua:

command --feature

command --no-feature

argparse hỗ trợ phiên bản này độc đáo:

parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)

Tất nhiên, nếu bạn thực sự muốn --arg <True|False>phiên bản, bạn có thể chuyển ast.literal_evaldưới dạng "loại" hoặc chức năng do người dùng xác định ...

def t_or_f(arg):
    ua = str(arg).upper()
    if 'TRUE'.startswith(ua):
       return True
    elif 'FALSE'.startswith(ua):
       return False
    else:
       pass  #error condition maybe?

96
Tôi vẫn nghĩ type=boolnên làm việc ra khỏi hộp (xem xét các đối số vị trí!). Ngay cả khi bạn chỉ định bổ sung choices=[False,True], bạn vẫn kết thúc bằng cả "Sai" và "Đúng" được coi là Đúng (do chuyển từ chuỗi sang bool?). Có thể vấn đề liên quan
cá heo

41
Phải, tôi chỉ nghĩ rằng không có lời biện minh nào cho việc này không hoạt động như mong đợi. Và điều này là vô cùng sai lệch, vì không có kiểm tra an toàn cũng như thông báo lỗi.
cá heo

69
@mgilson - Điều tôi thấy sai lệch là bạn có thể đặt type = bool, bạn không nhận được thông báo lỗi, tuy nhiên, đối với cả hai đối số chuỗi "Sai" và "Đúng", bạn nhận được True trong biến được cho là boolean của mình (do cách loại công trình đúc trong python). Vì vậy, loại = bool nên được hỗ trợ rõ ràng (phát ra một số cảnh báo, lỗi, v.v.) hoặc nó sẽ hoạt động theo cách hữu ích và được mong đợi bằng trực giác.
cá heo

14
@dolphin - tương ứng, tôi không đồng ý. Tôi nghĩ rằng hành vi này chính xác theo cách cần phải và phù hợp với zen của python "Các trường hợp đặc biệt không đủ đặc biệt để phá vỡ các quy tắc". Tuy nhiên, nếu bạn cảm thấy điều này mạnh mẽ về nó, tại sao không đưa nó lên một trong những danh sách gửi thư trăn khác nhau ? Ở đó, bạn có thể có cơ hội thuyết phục ai đó có khả năng làm điều gì đó về vấn đề này. Ngay cả khi bạn có thể thuyết phục tôi, bạn sẽ chỉ thành công trong việc thuyết phục tôi và hành vi vẫn không thay đổi vì tôi không phải là một nhà phát triển :)
mgilson

15
Có phải chúng ta đang tranh luận về những gì bool()hàm Python nên làm, hoặc những gì argparse nên chấp nhận type=fn? Tất cả các argparsekiểm tra là có fnthể gọi được. Nó dự kiến ​​sẽ fnlấy một đối số chuỗi và trả về một giá trị. Hành vi của fnlà trách nhiệm của lập trình viên, không argparse's.
hpaulj

235

Tôi khuyên bạn nên trả lời mgilson nhưng với một nhóm loại trừ lẫn nhau
để bạn không thể sử dụng --feature--no-featurecùng một lúc.

command --feature

command --no-feature

nhưng không

command --feature --no-feature

Kịch bản:

feature_parser = parser.add_mutually_exclusive_group(required=False)
feature_parser.add_argument('--feature', dest='feature', action='store_true')
feature_parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)

Sau đó, bạn có thể sử dụng trình trợ giúp này nếu bạn định đặt nhiều trong số chúng:

def add_bool_arg(parser, name, default=False):
    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument('--' + name, dest=name, action='store_true')
    group.add_argument('--no-' + name, dest=name, action='store_false')
    parser.set_defaults(**{name:default})

add_bool_arg(parser, 'useful-feature')
add_bool_arg(parser, 'even-more-useful-feature')

5
@CharlieParker add_argumentđược gọi với dest='feature'. set_defaultsđược gọi với feature=True. Hiểu biết?
fnkr

4
Câu trả lời này hoặc mgilson nên là câu trả lời được chấp nhận - mặc dù OP muốn --flag False, một phần câu trả lời SO phải là về NHỮNG GÌ họ đang cố gắng giải quyết, không chỉ về CÁCH. Hoàn toàn không có lý do để làm --flag Falsehoặc --other-flag Truesau đó sử dụng một số trình phân tích cú pháp tùy chỉnh để chuyển đổi chuỗi thành boolean .. action='store_true'action='store_false'là cách tốt nhất để sử dụng cờ boolean
kevlarr 20/03/18

6
@cowlinator Tại sao SO cuối cùng lại trả lời "các câu hỏi như đã nêu"? Theo hướng dẫn riêng của mình , một anwer ... can be “don’t do that”, but it should also include “try this instead”mà (ít nhất là với tôi) ngụ ý câu trả lời nên đi sâu hơn khi thích hợp. Chắc chắn sẽ có lúc một số người trong chúng ta đăng câu hỏi có thể được hưởng lợi từ hướng dẫn về các thực tiễn tốt hơn / tốt nhất, v.v. Trả lời "như đã nêu" thường không làm điều đó. Điều đó đang được nói, sự thất vọng của bạn với câu trả lời thường cho rằng quá nhiều (hoặc không chính xác) là hoàn toàn hợp lệ.
kevlarr

2
Nếu một người muốn có giá trị thứ ba khi người dùng chưa chỉ định rõ ràng tính năng, anh ta cần thay thế dòng cuối cùng bằngparser.set_defaults(feature=None)
Alex Che

2
Nếu chúng ta muốn thêm một help=mục cho đối số này, nó nên đi đâu? Trong add_mutually_exclusive_group()cuộc gọi? Trong một hoặc cả hai add_argument()cuộc gọi? Ở đâu đó khác?
Ken Williams

57

Đây là một biến thể khác không có thêm hàng / s để đặt giá trị mặc định. Bool luôn có một giá trị được gán để nó có thể được sử dụng trong các câu lệnh logic mà không cần kiểm tra trước.

import argparse
parser = argparse.ArgumentParser(description="Parse bool")
parser.add_argument("--do-something", default=False, action="store_true" , help="Flag to do something")
args = parser.parse_args()

if args.do_something:
     print("Do something")
else:
     print("Don't do something")
print("Check that args.do_something=" + str(args.do_something) + " is always a bool")

5
Câu trả lời này được đánh giá thấp, nhưng tuyệt vời trong sự đơn giản của nó. Đừng cố thiết lập required=Truenếu không bạn sẽ luôn nhận được một đối số đúng.
Garren S

1
Vui lòng KHÔNG BAO GIỜ sử dụng toán tử đẳng thức trên những thứ như bool hoặc nonetype. Bạn nên sử dụng IS thay thế
webKnjaZ

2
Đây là một câu trả lời tốt hơn so với chấp nhận vì nó chỉ đơn giản kiểm tra sự hiện diện của cờ để đặt giá trị boolean, thay vì yêu cầu chuỗi boolean dự phòng. (Yo dawg, tôi nghe nói bạn thích booleans ... vì vậy tôi đã đưa cho bạn một boolean với boolean của bạn để đặt boolean của bạn!)
Siphon

4
Hmm ... câu hỏi, như đã nêu, dường như muốn sử dụng "Đúng" / "Sai" trên chính dòng lệnh; tuy nhiên với ví dụ này, python3 test.py --do-something Falsekhông thành công error: unrecognized arguments: False, vì vậy nó không thực sự trả lời câu hỏi.
sdbbs

38

lót:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))

4
tốt cho người hâm mộ oneliner, cũng có thể được cải thiện một chút:type=lambda x: (str(x).lower() in ['true','1', 'yes'])
Tu Bùi

35

Dường như có một số nhầm lẫn về những gì type=booltype='bool'có thể có nghĩa. Một (hoặc cả hai) có nghĩa là 'chạy chức năng bool(), hoặc' trả lại một boolean '? Vì nó đứng type='bool'có nghĩa là không có gì. add_argumentđưa ra một 'bool' is not callablelỗi, giống như khi bạn sử dụng type='foobar', hoặc type='int'.

Nhưng argparsecó đăng ký cho phép bạn xác định các từ khóa như thế này. Nó chủ yếu được sử dụng cho action, ví dụ `action = 'store_true'. Bạn có thể thấy các từ khóa đã đăng ký với:

parser._registries

trong đó hiển thị một từ điển

{'action': {None: argparse._StoreAction,
  'append': argparse._AppendAction,
  'append_const': argparse._AppendConstAction,
...
 'type': {None: <function argparse.identity>}}

Có rất nhiều hành động được xác định, nhưng chỉ có một loại, loại mặc định , argparse.identity.

Mã này xác định từ khóa 'bool':

def str2bool(v):
  #susendberg's function
  return v.lower() in ("yes", "true", "t", "1")
p = argparse.ArgumentParser()
p.register('type','bool',str2bool) # add type keyword to registries
p.add_argument('-b',type='bool')  # do not use 'type=bool'
# p.add_argument('-b',type=str2bool) # works just as well
p.parse_args('-b false'.split())
Namespace(b=False)

parser.register()không được ghi lại, nhưng cũng không bị ẩn. Đối với hầu hết các phần, lập trình viên không cần biết về nó bởi vìtypeactionlấy các giá trị hàm và lớp. Có rất nhiều ví dụ stackoverflow về việc xác định giá trị tùy chỉnh cho cả hai.


Trong trường hợp không rõ ràng từ các cuộc thảo luận trước đó, bool() không có nghĩa là 'phân tích một chuỗi'. Từ tài liệu Python:

bool (x): Chuyển đổi một giá trị thành Boolean, sử dụng quy trình kiểm tra sự thật tiêu chuẩn.

Tương phản điều này với

int (x): Chuyển đổi một số hoặc chuỗi x thành một số nguyên.


3
Hoặc sử dụng: Parser.register ('type', 'bool', (lambda x: x.lower () in ("yes", "true", "t", "1")))
Matyas

17

Tôi đã tìm kiếm cùng một vấn đề, và imho giải pháp khá là:

def str2bool(v):
  return v.lower() in ("yes", "true", "t", "1")

và sử dụng điều đó để phân tích chuỗi thành boolean như đề xuất ở trên.


5
Nếu bạn đang đi theo con đường này, tôi có thể đề nghị distutils.util.strtobool(v).
CivilFan

1
Trả distutils.util.strtoboolvề 1 hoặc 0, không phải là boolean thực tế.
CMCDragonkai

14

Một cách khá giống nhau là sử dụng:

feature.add_argument('--feature',action='store_true')

và nếu bạn đặt đối số - tính năng trong lệnh của bạn

 command --feature

đối số sẽ là True, nếu bạn không đặt loại - hãy đảm bảo mặc định đối số luôn là Sai!


1
Có một số nhược điểm đối với phương pháp này mà các câu trả lời khác khắc phục? Đây dường như là giải pháp đơn giản nhất, ngắn gọn nhất đạt được những gì OP (và trong trường hợp này là tôi) muốn. Tôi thích nó.
Simon O'Hanlon

2
Trong khi đơn giản, nó không trả lời câu hỏi. OP muốn một đối số mà bạn có thể chỉ định--feature False
Astariul


12

Điều này hoạt động cho tất cả mọi thứ tôi mong đợi nó:

add_boolean_argument(parser, 'foo', default=True)
parser.parse_args([])                   # Whatever the default was
parser.parse_args(['--foo'])            # True
parser.parse_args(['--nofoo'])          # False
parser.parse_args(['--foo=true'])       # True
parser.parse_args(['--foo=false'])      # False
parser.parse_args(['--foo', '--nofoo']) # Error

Mật mã:

def _str_to_bool(s):
    """Convert string to bool (in argparse context)."""
    if s.lower() not in ['true', 'false']:
        raise ValueError('Need bool; got %r' % s)
    return {'true': True, 'false': False}[s.lower()]

def add_boolean_argument(parser, name, default=False):                                                                                               
    """Add a boolean argument to an ArgumentParser instance."""
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        '--' + name, nargs='?', default=default, const=True, type=_str_to_bool)
    group.add_argument('--no' + name, dest=name, action='store_false')

Thông minh! Tôi sẽ đi với câu trả lời này. Tôi đã điều chỉnh _str_to_bool(s)để chuyển đổi s = s.lower()một lần, sau đó kiểm tra if s not in {'true', 'false', '1', '0'}và cuối cùng return s in {'true', '1'}.
Jerry101

6

Một cách đơn giản hơn sẽ được sử dụng như dưới đây.

parser.add_argument('--feature', type=lambda s: s.lower() in ['true', 't', 'yes', '1'])

5

Đơn giản nhất. Nó không linh hoạt, nhưng tôi thích sự đơn giản.

  parser.add_argument('--boolean_flag',
                      help='This is a boolean flag.',
                      type=eval, 
                      choices=[True, False], 
                      default='True')

EDIT: Nếu bạn không tin tưởng đầu vào, đừng sử dụng eval.


Điều này có vẻ khá thuận tiện. Tôi nhận thấy bạn có eval là loại. Tôi đã có một câu hỏi về điều này: nên xác định eval như thế nào, hoặc có yêu cầu nhập khẩu để sử dụng nó không?
edesz

1
evallà một chức năng tích hợp. docs.python.org/3/l Library / fiances.html # eval Đây có thể là bất kỳ chức năng đơn nguyên nào mà các cách tiếp cận linh hoạt hơn khác tận dụng.
Russell

Này, thật tuyệt. Cảm ơn!
edesz

2
thật dễ thương, nhưng khá mạo hiểm khi chỉ đưa ra ngoài tự nhiên nơi những người dùng không biết xấu xa sẽ chỉ sao chép-dán nó vào kịch bản của họ.
Arne

@Arne, điểm tốt. Mặc dù, có vẻ như sẽ rất khó để một người dùng có thiện chí vô tình làm điều gì đó nguy hiểm.
Russell

3

Cách đơn giản nhất là sử dụng các lựa chọn :

parser = argparse.ArgumentParser()
parser.add_argument('--my-flag',choices=('True','False'))

args = parser.parse_args()
flag = args.my_flag == 'True'
print(flag)

Không vượt qua --my-flag đánh giá thành Sai. Các yêu cầu = True tùy chọn có thể được thêm vào nếu bạn luôn muốn người sử dụng để xác định một cách rõ ràng sự lựa chọn.


2

Tôi nghĩ rằng cách kinh điển nhất sẽ là:

parser.add_argument('--ensure', nargs='*', default=None)

ENSURE = config.ensure is None

1
class FlagAction(argparse.Action):
    # From http://bugs.python.org/issue8538

    def __init__(self, option_strings, dest, default=None,
                 required=False, help=None, metavar=None,
                 positive_prefixes=['--'], negative_prefixes=['--no-']):
        self.positive_strings = set()
        self.negative_strings = set()
        for string in option_strings:
            assert re.match(r'--[A-z]+', string)
            suffix = string[2:]
            for positive_prefix in positive_prefixes:
                self.positive_strings.add(positive_prefix + suffix)
            for negative_prefix in negative_prefixes:
                self.negative_strings.add(negative_prefix + suffix)
        strings = list(self.positive_strings | self.negative_strings)
        super(FlagAction, self).__init__(option_strings=strings, dest=dest,
                                         nargs=0, const=None, default=default, type=bool, choices=None,
                                         required=required, help=help, metavar=metavar)

    def __call__(self, parser, namespace, values, option_string=None):
        if option_string in self.positive_strings:
            setattr(namespace, self.dest, True)
        else:
            setattr(namespace, self.dest, False)

1

Cách đơn giản và đúng đắn nhất là

from distutils import util
arser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x)))

Xin lưu ý rằng các giá trị True là y, yes, t, true, on và 1; giá trị sai là n, không, f, false, off và 0. Tăng ValueError nếu val là bất cứ thứ gì khác.


0

Nhanh chóng và dễ dàng, nhưng chỉ cho các đối số 0 hoặc 1:

parser.add_argument("mybool", default=True,type=lambda x: bool(int(x)))
myargs=parser.parse_args()
print(myargs.mybool)

Đầu ra sẽ là "Sai" sau khi gọi từ thiết bị đầu cuối:

python myscript.py 0

-1

Tương tự như @Akash nhưng đây là một cách tiếp cận khác mà tôi đã sử dụng. Nó sử dụng strhơn lambdavì trăn lambdaluôn mang đến cho tôi cảm giác xa lạ.

import argparse
from distutils.util import strtobool

parser = argparse.ArgumentParser()
parser.add_argument("--my_bool", type=str, default="False")
args = parser.parse_args()

if bool(strtobool(args.my_bool)) is True:
    print("OK")

-1

Để cải thiện câu trả lời của @Akash Desarda, bạn có thể làm

import argparse
from distutils.util import strtobool

parser = argparse.ArgumentParser()
parser.add_argument("--foo", 
    type=lambda x:bool(strtobool(x)),
    nargs='?', const=True, default=False)
args = parser.parse_args()
print(args.foo)

Và nó hỗ trợ python test.py --foo

(base) [costa@costa-pc code]$ python test.py
False
(base) [costa@costa-pc code]$ python test.py --foo 
True
(base) [costa@costa-pc code]$ python test.py --foo True
True
(base) [costa@costa-pc code]$ python test.py --foo False
False
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.