Python argparse: Yêu cầu ít nhất một đối số


92

Tôi đã sử dụng argparsecho một chương trình Python có thể -process, -uploadhoặc cả hai:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('-process', action='store_true')
parser.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Chương trình là vô nghĩa nếu không có ít nhất một tham số. Làm cách nào để tôi có thể cấu hình argparseđể buộc ít nhất một tham số được chọn?

CẬP NHẬT:

Sau các nhận xét: Cách Pythonic để tham số hóa một chương trình với ít nhất một tùy chọn là gì?


9
-xthường là một lá cờ và tùy chọn. Cắt -nếu nó được yêu cầu.

1
Bạn không thể thực hiện processhành vi mặc định (mà không cần chỉ định bất kỳ tùy chọn nào) và cho phép người dùng thay đổi hành vi đó uploadnếu tùy chọn đó được đặt? Thông thường, các tùy chọn nên là tùy chọn, do đó có tên. Nên tránh các tùy chọn bắt buộc (điều này cũng có trong argparse tài liệu).
Tim Pietzcker,

@AdamMatan Đã gần ba năm kể từ khi bạn đặt câu hỏi của mình nhưng tôi thích thử thách ẩn trong đó và sử dụng lợi thế của các giải pháp mới có sẵn cho loại nhiệm vụ này.
Jan Vlcinsky

Câu trả lời:


107
if not (args.process or args.upload):
    parser.error('No action requested, add -process or -upload')

1
Đó có lẽ là cách duy nhất, nếu argparsekhông có tùy chọn tích hợp cho việc này.
Adam Matan

29
args = vars(parser.parse_args())
if not any(args.values()):
    parser.error('No arguments provided.')

3
+1 cho một giải pháp tổng quát. Cũng giống như việc sử dụng vars(), cũng hữu ích để chuyển các tùy chọn được đặt tên cẩn thận đến một phương thức khởi tạo với **.
Lenna

Đó chính xác là những gì tôi đang làm với nó. Cảm ơn!
brentlance

1
Dang, tôi thích điều đó vars. Tôi vừa làm .__dict__và cảm thấy chết lặng trước đó.
Theo Belaire

1
câu trả lời tuyệt vời. Cả hai "vars" và "bất kỳ" là mới với tôi :-)
Vivek Jha

21

Nếu không phải là phần 'hoặc cả hai' (ban đầu tôi đã bỏ qua phần này), bạn có thể sử dụng một cái gì đó như sau:

parser = argparse.ArgumentParser(description='Log archiver arguments.')
parser.add_argument('--process', action='store_const', const='process', dest='mode')
parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
args = parser.parse_args()
if not args.mode:
    parser.error("One of --process or --upload must be given")

Mặc dù vậy, có lẽ sẽ tốt hơn nếu bạn sử dụng các lệnh con .


4
Tôi nghĩ anh ấy muốn cho phép --processHOẶC --upload, không phải XOR. Điều này ngăn không cho cả hai tùy chọn được đặt cùng một lúc.
phihag

+1 vì bạn đã đề cập đến các lệnh con. Tuy nhiên - như ai đó đã chỉ ra trong các nhận xét -x--xxxthường là các tham số tùy chọn.
mac

20

Tôi biết điều này đã cũ như bụi bẩn, nhưng cách yêu cầu một tùy chọn nhưng cấm nhiều hơn một (XOR) là như thế này:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()
print args

Đầu ra:

>opt.py  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: one of the arguments -process -upload is required  

>opt.py -upload  
Namespace(process=False, upload=True)  

>opt.py -process  
Namespace(process=True, upload=False)  

>opt.py -upload -process  
usage: multiplot.py [-h] (-process | -upload)  
multiplot.py: error: argument -process: not allowed with argument -upload  

3
Thật không may, OP không muốn một XOR. Đó là một trong hai hoặc cả hai, nhưng không phải không có vì vậy trường hợp thử nghiệm cuối cùng của bạn không đáp ứng yêu cầu của họ.
kdopen

2
@kdopen: người trả lời đã làm rõ rằng đây là một biến thể của câu hỏi ban đầu, mà tôi thấy hữu ích: "cách yêu cầu một tùy chọn nhưng cấm nhiều hơn một" Có lẽ nghi thức của Stack Exchange sẽ yêu cầu một câu hỏi mới thay thế . Nhưng có câu trả lời có mặt ở đây đã giúp tôi ...
erik.weathers

2
Bài đăng này không trả lời câu hỏi đầu tiên
Marc

2
Làm thế nào để trả lời câu hỏi về "ít nhất một"?
xaxxon 22/07/19

2
Thật không may, OP không muốn một XOR.
duckman_1991

8

Xem xét yêu cầu

  • sử dụng argparse(Tôi sẽ bỏ qua cái này)
  • cho phép một hoặc hai hành động được gọi (ít nhất một hành động bắt buộc).
  • cố gắng bởi Pythonic (tôi muốn gọi nó là giống như "POSIX")

Ngoài ra còn có một số yêu cầu ngầm khi sử dụng dòng lệnh:

  • giải thích cách sử dụng cho người dùng theo cách dễ hiểu
  • các tùy chọn sẽ là tùy chọn
  • cho phép chỉ định cờ và tùy chọn
  • cho phép kết hợp với các tham số khác (như tên hoặc tên tệp).

Giải pháp mẫu sử dụng docopt(tệp managelog.py):

"""Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Cố gắng chạy nó:

$ python managelog.py
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Hiển thị sự trợ giúp:

$ python managelog.py -h
Manage logfiles
Usage:
    managelog.py [options] process -- <logfile>...
    managelog.py [options] upload -- <logfile>...
    managelog.py [options] process upload -- <logfile>...
    managelog.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>

Và sử dụng nó:

$ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
{'--': True,
 '--pswd': 'secret',
 '--user': 'user',
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': False,
 'upload': True}

Thay thế ngắn short.py

Thậm chí có thể có biến thể ngắn hơn:

"""Manage logfiles
Usage:
    short.py [options] (process|upload)... -- <logfile>...
    short.py -h

Options:
    -V, --verbose      Be verbose
    -U, --user <user>  Username
    -P, --pswd <pswd>  Password

Manage log file by processing and/or uploading it.
If upload requires authentication, you shall specify <user> and <password>
"""
if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args

Cách sử dụng trông như thế này:

$ python short.py -V process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 1,
 'upload': 1}

Lưu ý rằng thay vì các giá trị boolean cho các khóa "process" và "upload" thì có bộ đếm.

Hóa ra, chúng ta không thể ngăn chặn sự trùng lặp của những từ này:

$ python short.py -V process process upload  -- alfa.log beta.log
{'--': True,
 '--pswd': None,
 '--user': None,
 '--verbose': True,
 '-h': False,
 '<logfile>': ['alfa.log', 'beta.log'],
 'process': 2,
 'upload': 1}

Kết luận

Đôi khi, việc thiết kế giao diện dòng lệnh tốt có thể là một thách thức.

Có nhiều khía cạnh của chương trình dựa trên dòng lệnh:

  • thiết kế tốt dòng lệnh
  • chọn / sử dụng trình phân tích cú pháp thích hợp

argparse cung cấp rất nhiều, nhưng hạn chế các tình huống có thể xảy ra và có thể trở nên rất phức tạp.

Mọi docoptthứ diễn ra ngắn hơn nhiều trong khi vẫn đảm bảo khả năng đọc và cung cấp mức độ linh hoạt cao. Nếu bạn quản lý việc nhận các đối số được phân tích cú pháp từ từ điển và thực hiện một số chuyển đổi (thành số nguyên, mở tệp ..) theo cách thủ công (hoặc bằng thư viện khác được gọi schema), bạn có thể thấy docoptphù hợp với phân tích cú pháp dòng lệnh.


Chưa bao giờ nghe nói về docopt, gợi ý tuyệt vời!
Ton van den Heuvel

@TonvandenHeuvel Tốt. Tôi chỉ muốn xác nhận rằng, tôi vẫn đang sử dụng nó làm giải pháp ưa thích cho giao diện dòng lệnh.
Jan Vlcinsky 19/02/15

Câu trả lời tốt nhất evar, cảm ơn bạn vì các ví dụ chi tiết.
jnovack

5

Nếu bạn yêu cầu chương trình python chạy với ít nhất một tham số, hãy thêm đối số không có tiền tố tùy chọn (- hoặc - theo mặc định) và đặt nargs=+(Yêu cầu tối thiểu một đối số). Vấn đề với phương pháp này mà tôi tìm thấy là nếu bạn không chỉ định đối số, argparse sẽ tạo ra lỗi "quá ít đối số" và không in ra menu trợ giúp. Nếu bạn không cần chức năng đó, đây là cách thực hiện trong mã:

import argparse

parser = argparse.ArgumentParser(description='Your program description')
parser.add_argument('command', nargs="+", help='describe what a command is')
args = parser.parse_args()

Tôi nghĩ rằng khi bạn thêm một đối số với các tiền tố tùy chọn, tường thuật chi phối toàn bộ trình phân tích cú pháp đối số chứ không chỉ tùy chọn. (Ý tôi là, nếu bạn có --optioncờ với nargs="+", thì --optioncờ sẽ mong đợi ít nhất một đối số. Nếu bạn có optionvới nargs="+", nó mong đợi ít nhất một đối số tổng thể.)


Bạn có thể thêm choices=['process','upload']vào đối số đó.
hpaulj

5

Đối với http://bugs.python.org/issue11588, tôi đang khám phá các cách tổng quát hóa mutually_exclusive_groupkhái niệm để xử lý các trường hợp như thế này.

Với sự phát triển này argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py Tôi có thể viết:

parser = argparse.ArgumentParser(prog='PROG', 
    description='Log archiver arguments.')
group = parser.add_usage_group(kind='any', required=True,
    title='possible actions (at least one is required)')
group.add_argument('-p', '--process', action='store_true')
group.add_argument('-u', '--upload',  action='store_true')
args = parser.parse_args()
print(args)

tạo ra những thứ sau help:

usage: PROG [-h] (-p | -u)

Log archiver arguments.

optional arguments:
  -h, --help     show this help message and exit

possible actions (at least one is required):
  -p, --process
  -u, --upload

Điều này chấp nhận các đầu vào như '-u', '-up', '--proc --up', v.v.

Nó kết thúc chạy thử nghiệm tương tự như https://stackoverflow.com/a/6723066/901925 , mặc dù thông báo lỗi cần phải rõ ràng hơn:

usage: PROG [-h] (-p | -u)
PROG: error: some of the arguments process upload is required

Tôi tự hỏi:

  • các tham số kind='any', required=Truecó đủ rõ ràng không (chấp nhận bất kỳ nhóm nào; ít nhất một tham số là bắt buộc)?

  • cách sử dụng có (-p | -u)rõ ràng không? Một nhóm lẫn nhau bắt buộc tạo ra điều tương tự. Có một số ký hiệu thay thế?

  • Sử dụng một nhóm như thế này có trực quan hơn so với phihag'sthử nghiệm đơn giản không?


Tôi không thể tìm thấy bất kỳ đề cập nào add_usage_grouptrên trang này: docs.python.org/2/library/argparse.html ; bạn vui lòng cung cấp một liên kết đến tài liệu cho nó?
P. Myer Nore

@ P.MyerNore, tôi đã cung cấp một liên kết - ở đầu câu trả lời này. Điều này đã không được đưa vào sản xuất.
hpaulj

5

Cách tốt nhất để làm điều này là sử dụng mô-đun sẵn có của python add_mutently_exclusive_group .

parser = argparse.ArgumentParser(description='Log archiver arguments.')
group = parser.add_mutually_exclusive_group()
group.add_argument('-process', action='store_true')
group.add_argument('-upload',  action='store_true')
args = parser.parse_args()

Nếu bạn chỉ muốn một đối số được chọn bằng dòng lệnh, chỉ cần sử dụng Required = True làm đối số cho nhóm

group = parser.add_mutually_exclusive_group(required=True)

2
Làm thế nào điều này giúp bạn có "ít nhất một" - nó không giúp bạn có "chính xác một"?
xaxxon 22/07/19

3
Thật không may, OP không muốn một XOR. OP đang tìm kiếm HOẶC
duckman_1991

Câu này không trả lời câu hỏi của OP, nhưng nó đã trả lời câu hỏi của tôi nên dù sao cũng cảm ơn ¯_ (ツ) _ / ¯
rosstex

2

Có thể sử dụng trình phân tích cú pháp phụ?

import argparse

parser = argparse.ArgumentParser(description='Log archiver arguments.')
subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
parser_process = subparsers.add_parser('process', help='Process logs')
parser_upload = subparsers.add_parser('upload', help='Upload logs')
args = parser.parse_args()

print("Subparser: ", args.subparser_name)

Hiện tại --helpcho thấy:

$ python /tmp/aaa.py --help
usage: aaa.py [-h] {process,upload} ...

Log archiver arguments.

positional arguments:
  {process,upload}  sub-command help
    process         Process logs
    upload          Upload logs

optional arguments:
  -h, --help        show this help message and exit
$ python /tmp/aaa.py
usage: aaa.py [-h] {process,upload} ...
aaa.py: error: too few arguments
$ python3 /tmp/aaa.py upload
Subparser:  upload

Bạn cũng có thể thêm các tùy chọn bổ sung cho các trình phân tích cú pháp con này. Ngoài ra, thay vì sử dụng điều đó, dest='subparser_name'bạn cũng có thể ràng buộc các hàm được gọi trực tiếp trên lệnh con đã cho (xem tài liệu).


2

Điều này đạt được mục đích và điều này cũng sẽ được hoàn thiện trong --helpđầu ra được tạo tự động của argparse , đây là điều mà hầu hết các lập trình viên lành nghề muốn (cũng hoạt động với các đối số tùy chọn):

parser.add_argument(
    'commands',
    nargs='+',                      # require at least 1
    choices=['process', 'upload'],  # restrict the choice
    help='commands to execute'
)

Tài liệu chính thức về điều này: https://docs.python.org/3/library/argparse.html#choices


1

Sử dụng append_const vào danh sách các hành động và sau đó kiểm tra xem danh sách đã được điền chưa:

parser.add_argument('-process', dest=actions, const="process", action='append_const')
parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')

args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

Bạn thậm chí có thể chỉ định các phương thức trực tiếp trong các hằng số.

def upload:
    ...

parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
args = parser.parse_args()

if(args.actions == None):
    parser.error('Error: No actions requested')

else:
    for action in args.actions:
        action()
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.