Parsing nilai boolean dengan argparse

616

Saya ingin menggunakan argumen argparse untuk mem-parsing baris perintah boolean yang ditulis sebagai "--foo True" atau "--foo False". Sebagai contoh:

my_program --my_boolean_flag False

Namun, kode tes berikut tidak melakukan apa yang saya inginkan:

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)

Sedihnya, parsed_args.my_booldievaluasi menjadi True. Ini adalah kasus bahkan ketika saya berubah cmd_linemenjadi ["--my_bool", ""], yang mengejutkan, sejak bool("")dievakuasi False.

Bagaimana saya bisa mendapatkan argparse untuk mengurai "False", "F"dan huruf kecil mereka varian untuk menjadi False?

SuperElectric
sumber
40
Berikut ini adalah interpretasi satu-baris dari jawaban @ mgilson parser.add_argument('--feature', dest='feature', default=False, action='store_true') . Solusi ini akan menjamin Anda selalu mendapatkan booljenis dengan nilai Trueatau False. (Solusi ini memiliki kendala: opsi Anda harus memiliki nilai default.)
Trevor Boyd Smith
7
Berikut ini adalah interpretasi satu-baris dari jawaban @ Maxim parser.add_argument('--feature', dest='feature', type=lambda x:bool(distutils.util.strtobool(x))) . Ketika opsi ini digunakan, solusi ini akan memastikan booljenis dengan nilai Trueatau False. Ketika opsi tidak digunakan, Anda akan mendapatkan None. ( distutils.util.strtobool(x)dari pertanyaan stackoverflow lainnya )
Trevor Boyd Smith
8
bagaimana dengan sesuatu sepertiparser.add_argument('--my_bool', action='store_true', default=False)
AruniRC

Jawaban:

276

Namun solusi lain menggunakan saran sebelumnya, tetapi dengan kesalahan parse "benar" dari 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.')

Ini sangat berguna untuk membuat sakelar dengan nilai default; contohnya

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

memungkinkan saya untuk menggunakan:

script --nice
script --nice <bool>

dan masih menggunakan nilai default (khusus untuk pengaturan pengguna). Satu kelemahan (terkait tidak langsung) dengan pendekatan itu adalah bahwa 'nargs' mungkin menangkap argumen posisi - lihat pertanyaan terkait ini dan laporan bug argparse ini .

Pepatah
sumber
4
nargs = '?' berarti nol atau satu argumen. docs.python.org/3/library/argparse.html#nargs
Maxim
1
Saya suka ini, tapi padanan saya dengan default = NICE memberi saya kesalahan, jadi saya harus melakukan sesuatu yang lain.
Michael Mathews
2
@MarcelloRomani str2bool bukan tipe dalam arti Python, itu adalah fungsi yang didefinisikan di atas, Anda perlu memasukkannya di suatu tempat.
Maxim
4
kode str2bool(v)dapat diganti dengan bool(distutils.util.strtobool(v)). Sumber: stackoverflow.com/a/18472142/2436175
Antonio
4
Mungkin perlu disebutkan bahwa dengan cara ini Anda tidak dapat memeriksa apakah argumen diatur dengan if args.nice:karena jika argumen diatur ke False, itu tidak akan pernah melewati kondisi. Jika ini benar maka mungkin lebih baik untuk kembali daftar dari str2boolfungsi dan set daftar sebagai constparameter, seperti ini [True], [False]. Perbaiki saya jika saya salah
NutCracker
889

Saya pikir cara yang lebih kanonik untuk melakukan ini adalah melalui:

command --feature

dan

command --no-feature

argparse mendukung versi ini dengan baik:

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

Tentu saja, jika Anda benar-benar menginginkan --arg <True|False>versi tersebut, Anda dapat lulus ast.literal_evalsebagai "tipe", atau fungsi yang ditentukan pengguna ...

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?
mgilson
sumber
96
Saya masih berpikir type=boolharus bekerja di luar kotak (pertimbangkan argumen posisi!). Bahkan ketika Anda menentukan tambahan choices=[False,True], Anda berakhir dengan "False" dan "True" dianggap Benar (karena pemain dari string ke bool?). Mungkin masalah terkait
lumba
41
Benar, saya hanya berpikir tidak ada pembenaran untuk ini tidak berfungsi seperti yang diharapkan. Dan ini sangat menyesatkan, karena tidak ada pemeriksaan keamanan atau pesan kesalahan.
lumba
69
@mgilson - Apa yang saya temukan menyesatkan adalah bahwa Anda dapat mengatur type = bool, Anda tidak mendapatkan pesan kesalahan, namun, untuk argumen string "False" dan "True", Anda mendapatkan True dalam variabel boolean yang seharusnya (karena cara tipe casting bekerja dengan python). Jadi salah satu tipe = bool harus jelas tidak didukung (memancarkan beberapa peringatan, kesalahan, dll), atau itu harus bekerja dengan cara yang berguna dan diharapkan secara intuitif.
lumba
14
@ dolphin - masing-masing, saya tidak setuju. Saya pikir perilaku itu persis seperti yang seharusnya dan konsisten dengan zen python "Kasus khusus tidak cukup istimewa untuk melanggar aturan". Namun, jika Anda merasakan hal ini dengan kuat, mengapa tidak membawanya pada salah satu dari berbagai milis python ? Di sana, Anda mungkin memiliki kesempatan untuk meyakinkan seseorang yang memiliki kekuatan untuk melakukan sesuatu tentang masalah ini. Bahkan jika Anda dapat meyakinkan saya, Anda hanya akan berhasil meyakinkan saya dan perilaku itu masih tidak akan berubah karena saya bukan dev :)
mgilson
15
Apakah kita berdebat tentang apa yang bool()harus dilakukan fungsi Python , atau apa argumen yang harus diterima type=fn? Semua argparsecek adalah yang fnbisa dipanggil. Itu mengharapkan fnuntuk mengambil satu argumen string, dan mengembalikan nilai. Perilaku fnadalah tanggung jawab programmer, bukan argparse's.
hpaulj
235

Saya merekomendasikan jawaban mgilson tetapi dengan grup yang saling eksklusif
sehingga Anda tidak dapat menggunakan --featuredan --no-featurepada saat yang sama.

command --feature

dan

command --no-feature

tapi tidak

command --feature --no-feature

Naskah:

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)

Anda kemudian dapat menggunakan pembantu ini jika Anda akan mengatur banyak dari mereka:

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')
fnkr
sumber
5
@CharlieParker add_argumentdipanggil dengan dest='feature'. set_defaultsdisebut dengan feature=True. Memahami?
fnkr
4
Jawaban ini atau mgilson seharusnya menjadi jawaban yang diterima - meskipun OP ingin --flag False, bagian dari jawaban SO harus tentang APA yang mereka coba selesaikan, bukan hanya tentang BAGAIMANA. Seharusnya sama sekali tidak ada alasan untuk melakukan --flag Falseatau --other-flag Truemenggunakan parser khusus untuk mengonversi string menjadi boolean .. action='store_true'dan action='store_false'merupakan cara terbaik untuk menggunakan bendera boolean
kevlarr
6
@cowlinator Mengapa SO pada akhirnya menjawab "pertanyaan sebagaimana dinyatakan"? Menurut pedomannya sendiri , sebuah ... can be “don’t do that”, but it should also include “try this instead”jawaban yang (setidaknya bagi saya) menyiratkan jawaban harus masuk lebih dalam jika sesuai. Pasti ada saat-saat ketika sebagian dari kita memposting pertanyaan dapat mengambil manfaat dari panduan tentang praktik yang lebih baik / terbaik, dll. Menjawab "sebagaimana dinyatakan" sering kali tidak melakukannya. Yang sedang berkata, frustrasi Anda dengan jawaban sering menganggap terlalu banyak (atau salah) benar-benar valid.
kevlarr
2
Jika seseorang ingin memiliki nilai ketiga ketika pengguna belum menentukan fitur secara eksplisit, ia perlu mengganti baris terakhir denganparser.set_defaults(feature=None)
Alex Che
2
Jika kita ingin menambahkan help=entri untuk argumen ini, kemana perginya? Dalam add_mutually_exclusive_group()panggilan? Dalam satu atau kedua add_argument()panggilan? Di tempat lain?
Ken Williams
57

Berikut adalah variasi lain tanpa baris tambahan untuk menetapkan nilai default. Bool selalu memiliki nilai yang ditetapkan sehingga dapat digunakan dalam pernyataan logis tanpa pemeriksaan awal.

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")
Schaki
sumber
6
Jawaban ini diremehkan, tetapi indah dalam kesederhanaannya. Jangan mencoba mengatur required=Trueatau Anda akan selalu mendapatkan argumen True.
Garren S
1
Harap TIDAK PERNAH menggunakan operator persamaan pada hal-hal seperti bool atau nonetype. Anda harus menggunakan IS sebagai gantinya
webKnjaZ
2
Ini adalah jawaban yang lebih baik daripada yang diterima karena hanya memeriksa keberadaan bendera untuk menetapkan nilai boolean, alih-alih membutuhkan string boolean yang berlebihan. (Yo dawg, saya mendengar Anda menyukai boolean ... jadi saya memberi Anda boolean dengan boolean Anda untuk mengatur boolean Anda!)
Siphon
4
Hmm ... pertanyaannya, seperti yang dinyatakan, tampaknya ingin menggunakan "Benar" / "Salah" pada baris perintah itu sendiri; namun dengan contoh ini, python3 test.py --do-something Falsegagal dengan error: unrecognized arguments: False, sehingga tidak benar-benar menjawab pertanyaan.
sdbbs
38

oneliner:

parser.add_argument('--is_debug', default=False, type=lambda x: (str(x).lower() == 'true'))
Evalds Urtans
sumber
4
bagus untuk penggemar oneliner, juga bisa sedikit ditingkatkan:type=lambda x: (str(x).lower() in ['true','1', 'yes'])
Tu Bui
35

Tampaknya ada beberapa kebingungan tentang apa type=booldan apa type='bool'artinya. Haruskah satu (atau keduanya) berarti 'menjalankan fungsi bool(), atau' mengembalikan boolean '? Seperti berdiri type='bool'tidak ada artinya. add_argumentmemberikan 'bool' is not callablekesalahan, sama seperti jika Anda menggunakan type='foobar', atau type='int'.

Tetapi argparseapakah memiliki registri yang memungkinkan Anda menentukan kata kunci seperti ini. Ini sebagian besar digunakan untuk action, misalnya `action = 'store_true'. Anda dapat melihat kata kunci yang terdaftar dengan:

parser._registries

yang menampilkan kamus

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

Ada banyak tindakan yang ditentukan, tetapi hanya satu jenis, yang standar argparse.identity,.

Kode ini mendefinisikan kata kunci '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()tidak didokumentasikan, tetapi juga tidak disembunyikan. Untuk sebagian besar programmer tidak perlu tahu tentang hal itu karena typedan actionfungsi mengambil dan kelas nilai-nilai. Ada banyak contoh stackoverflow mendefinisikan nilai-nilai khusus untuk keduanya.


Dalam hal ini tidak jelas dari diskusi sebelumnya, bool()tidak berarti 'mengurai string'. Dari dokumentasi Python:

bool (x): Konversi nilai menjadi Boolean, menggunakan prosedur pengujian kebenaran standar.

Bandingkan ini dengan

int (x): Konversi angka atau string x ke integer.

hpaulj
sumber
3
Atau gunakan: parser.register ('type', 'bool', (lambda x: x.lower () in ("yes", "true", "t", "1")))
Matyas
17

Saya mencari masalah yang sama, dan solusi yang bagus adalah:

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

dan menggunakannya untuk mengurai string ke boolean seperti yang disarankan di atas.

susundberg
sumber
5
Jika Anda akan pergi rute ini, mungkin saya sarankan distutils.util.strtobool(v).
CivFan
1
Pengembalian distutils.util.strtobool1 atau 0, bukan boolean yang sebenarnya.
CMCDragonkai
14

Cara yang sangat mirip adalah dengan menggunakan:

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

dan jika Anda mengatur argumen - fitur dalam perintah Anda

 command --feature

argumennya adalah True, jika Anda tidak menyetel tipe --feature, argumen default selalu False!

dl.meteo
sumber
1
Apakah ada beberapa kelemahan pada metode ini yang diatasi oleh jawaban yang lain? Sejauh ini tampaknya ini adalah solusi termudah dan paling ringkas yang mencapai apa yang diinginkan OP (dan dalam hal ini saya). Aku menyukainya.
Simon O'Hanlon
2
Meski sederhana, itu tidak menjawab pertanyaan. OP ingin argumen di mana Anda dapat menentukan--feature False
Astariul
12

Ini berfungsi untuk semua yang saya harapkan:

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

Kode:

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')
Joe Pete yang kekar
sumber
Luar biasa! Saya akan menjawab ini. Saya men-tweak saya _str_to_bool(s)untuk mengkonversi s = s.lower()sekali, lalu menguji if s not in {'true', 'false', '1', '0'}, dan akhirnya return s in {'true', '1'}.
Jerry101
6

Cara yang lebih sederhana adalah menggunakan seperti di bawah ini.

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

Paling sederhana. Ini tidak fleksibel, tetapi saya lebih suka kesederhanaan.

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

EDIT: Jika Anda tidak percaya input, jangan gunakan eval.

Russell
sumber
Ini sepertinya cukup nyaman. Saya perhatikan Anda memiliki eval sebagai tipenya. Saya punya pertanyaan tentang ini: bagaimana eval harus didefinisikan, atau adakah impor yang diperlukan untuk memanfaatkannya?
edesz
1
evaladalah fungsi bawaan. docs.python.org/3/library/functions.html#eval Ini bisa merupakan fungsi yang tidak disadari yang memanfaatkan pendekatan lain yang lebih fleksibel.
Russell
Hei, bagus sekali. Terima kasih!
edesz
2
itu lucu, tapi cukup berisiko untuk dimasukkan ke dalam alam liar di mana pengguna yang tidak sadar akan kejahatan itu hanya akan menyalin dan menempelkannya ke dalam skrip mereka.
Arne
@ Andre, poin bagus. Meskipun, tampaknya akan sangat sulit bagi pengguna yang bermaksud baik untuk secara tidak sengaja melakukan sesuatu yang merusak.
Russell
3

Cara paling sederhana adalah menggunakan pilihan :

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

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

Tidak lewat --my-flag mengevaluasi ke False. Opsi diperlukan = Benar dapat ditambahkan jika Anda selalu ingin pengguna untuk secara eksplisit menentukan pilihan.

gerardw
sumber
2

Saya pikir cara yang paling kanonik adalah:

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

ENSURE = config.ensure is None
Andreas Maertens
sumber
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)
Robert T. McGibbon
sumber
1

Cara paling sederhana & paling benar adalah

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

Perhatikan bahwa nilai True adalah y, ya, t, benar, dan 1; nilai false adalah n, no, f, false, off dan 0. Meningkatkan ValueError jika val adalah hal lain.

Akash Desarda
sumber
0

Cepat dan mudah, tetapi hanya untuk argumen 0 atau 1:

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

Output akan menjadi "Salah" setelah menelepon dari terminal:

python myscript.py 0
FEMengineer
sumber
-1

Mirip dengan @Akash tapi di sini ada pendekatan lain yang pernah saya gunakan. Ini menggunakan strdaripada lambdakarena python lambdaselalu memberi saya perasaan asing.

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")
Youngjae
sumber
-1

Sebagai peningkatan jawaban @Akash Desarda, Anda bisa melakukannya

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)

Dan itu mendukung 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
Costa Huang
sumber