Home  >  Article  >  Backend Development  >  Use click to create the perfect Python command line program

Use click to create the perfect Python command line program

WBOY
WBOYforward
2023-04-18 14:55:031231browse

Use click to create the perfect Python command line program


The main job of Python programmers is to write command line programs, that is, scripts that run directly in the terminal. As the project grows in size, we hope to create an effective command line interface that can solve different problems by providing different parameters instead of modifying the source code every time.

In order to achieve this goal, I have summarized four principles, I hope it will be helpful to everyone:

  • Command line parameters should provide default values ​​
  • Handle all possibilities Parameter errors, including missing parameters, wrong data types, unable to find files, etc.
  • Write comprehensive documentation to explain the meaning of parameters and how to set them
  • Use a progress bar to display long-running tasks

A simple example

Let's apply these rules to a concrete case: a script that uses the Caesar cipher to encrypt and decrypt messages.

Suppose we write an encrypt function as shown below. Now let's create a script to encrypt and decrypt messages.

The script allows the user to select: mode (encryption or decryption), key. The default value for the former is encryption, and the default value for the latter is 1. This is all achieved through command line parameters.

def encrypt(plaintext, key):
cyphertext = ''
for character in plaintext:
if character.isalpha():
number = ord(character)
number += key
if character.isupper():
if number > ord('Z'):
number -= 26
elif number < ord('A'):
number += 26
elif character.islower():
if number > ord('z'):
number -= 26
elif number < ord('a'):
number += 26
character = chr(number)
cyphertext += character
return cyphertext

Beginner’s method: sys.argv

The script needs to get the value of the command line parameter first, let’s use the simplest sys.argv to implement it first.

sys.argv is a list that contains all the parameters entered by the user when running the script (including the script name itself).

Enter the following command in the terminal:

> python caesar_script.py --key 23 --decrypt my secret message
pb vhfuhw phvvdjh

sys.argv list includes:

['caesar_script.py', '--key', '23', '--decrypt', 'my', 'secret', 'message']

In order to obtain the parameter value, you need Loop through the argument list, looking for a '--key' (or '-k' ) to get the key value, and a '--decrypt' to get the mode.

import sys
from caesar_encryption import encryp
def caesar():
key = 1
is_error = False
for index, arg in enumerate(sys.argv):
if arg in ['--key', '-k'] and len(sys.argv) > index + 1:
key = int(sys.argv[index + 1])
del sys.argv[index]
del sys.argv[index]
break
for index, arg in enumerate(sys.argv):
if arg in ['--encrypt', '-e']:
del sys.argv[index]
break
if arg in ['--decrypt', '-d']:
key = -key
del sys.argv[index]
break
if len(sys.argv) == 1:
is_error = True
else:
for arg in sys.argv:
if arg.startswith('-'):
is_error = True
if is_error:
print(f'Usage: python {sys.argv[0]} [ --key <key> ] [ --encrypt|decrypt ] <text>')
else:
print(encrypt(' '.join(sys.argv[1:]), key))
if __name__ == '__main__':
caesar()

The code follows the principles we proposed at the beginning:

Have a default key value and a default mode

Handle basic errors (no input text or unknown parameters provided)

Print a concise prompt message when the parameters are wrong or the script is called without parameters

> python caesar_script_using_sys_argv.py
Usage: python caesar.py [ --key <key> ] [ --encrypt|decrypt ] <text>

But this version of the script is quite long (39 lines, not Including encryption functions), and the code is very ugly.

Is there a better way to parse command line arguments?

Enter argparse

argparse is a Python standard library module used to parse command line arguments.

Modify the script to use argparse to parse command line arguments:

import argparse
from caesar_encryption import encrypt
def caesar():
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-e', '--encrypt', action='store_true')
group.add_argument('-d', '--decrypt', action='store_true')
parser.add_argument('text', nargs='*')
parser.add_argument('-k', '--key', type=int, default=1)
args = parser.parse_args()
text_string = ' '.join(args.text)
key = args.key
if args.decrypt:
key = -key
cyphertext = encrypt(text_string, key)
print(cyphertext)
if __name__ == '__main__':
caesar()

The code still adheres to the principles we proposed and provides more precise documentation and More interactive error handling.

> python caesar_script_using_argparse.py --encode My message
usage: caesar_script_using_argparse.py [-h] [-e | -d] [-k KEY] [text [text ...]]
caesar_script_using_argparse.py: error: unrecognized arguments: --encode
> python caesar_script_using_argparse.py --help
usage: caesar_script_using_argparse.py [-h] [-e | -d] [-k KEY] [text [text ...]]positional arguments:
text
optional arguments:
-h, --help show this help message and exit
-e, --encrypt
-d, --decrypt
-k KEY, --key KEY

Lines 7 to 13 of the script define command line arguments, but they are not very elegant: too verbose and procedural, we could use something more compact and declarative way completed.

Use click to create a better command line interface

Fortunately, there is a third-party library click for creating a command line interface. It not only provides more functions than argparse, but also has a more code style. pretty. Replace argparse with click and continue optimizing the script.

import click
from caesar_encryption import encrypt
@click.command()
@click.argument('text', nargs=-1)
@click.option('--decrypt/--encrypt', '-d/-e')
@click.option('--key', '-k', default=1)
def caesar(text, decrypt, key):
text_string = ' '.join(text)
if decrypt:
key = -key
cyphertext = encrypt(text_string, key)
click.echo(cyphertext)
if __name__ == '__main__':
caesar()

Note that command line arguments and options are declared in decorators, which makes them directly accessible as function arguments.

Let us analyze the above code carefully:

nargs defines the number of values ​​​​received by the command line parameter. The default value is 1, and nargs=-1 allows any number of words to be provided.

--encrypt/--decrypt defines mutually exclusive options, which are ultimately passed to the program as a Boolean value.

click.echo is the basic function provided by the click library. Its function is similar to print, but it provides more powerful functions, such as adjusting the color of text printed to the console.

Read input from local file

The value received by the command line parameter is a top secret message that will be encrypted, so it may raise security concerns if the user is asked to enter plain text directly into the terminal .

A safer approach is to use hidden hints, or read the text from a local file, which is more practical for long texts.

The same idea applies to output: the user can save it to a file, or print it out in the terminal. Let's continue optimizing the script.

import click
from caesar_encryption import encrypt
@click.command()
@click.option(
'--input_file',
type=click.File('r'),
help='File in which there is the text you want to encrypt/decrypt.'
 'If not provided, a prompt will allow you to type the input text.',
)
@click.option(
'--output_file',
type=click.File('w'),
help='File in which the encrypted / decrypted text will be written.'
 'If not provided, the output text will just be printed.',
)
@click.option(
'--decrypt/--encrypt',
'-d/-e',
help='Whether you want to encrypt the input text or decrypt it.'
)
@click.option(
'--key',
'-k',
default=1,
help='The numeric key to use for the caesar encryption / decryption.'
)
def caesar(input_file, output_file, decrypt, key):
if input_file:
text = input_file.read()
else:
text = click.prompt('Enter a text', hide_input=not decrypt)
if decrypt:
key = -key
cyphertext = encrypt(text, key)
if output_file:
output_file.write(cyphertext)
else:
click.echo(cyphertext)
if __name__ == '__main__':
caesar()

As the script becomes more complex, we create a parameter document (implemented by defining the help parameter of the click.option decorator) to explain the function of the parameter in detail. The effect is as follows.

> python caesar_script_v2.py --help
Usage: caesar_script_v2.py [OPTIONS]
Options:
--input_file FILENAMEFile in which there is the text you want to encrypt/decrypt. If not provided, a prompt will allow you to type the input text.
--output_file FILENAME File in which the encrypted/decrypted text will be written. If not provided, the output text will just be printed.
-d, --decrypt / -e, --encryptWhether you want to encrypt the input text or decrypt it.
-k, --key INTEGERThe numeric key to use for the caesar encryption / decryption.
--help Show this message and exit.

We have two new parameters input_file and output_file, the type is click.File, click will open the file in the correct mode and handle possible errors. For example, the file cannot be found:

> python caesar_script_v2.py --decrypt --input_file wrong_file.txt
Usage: caesar_script_v2.py [OPTIONS]
Error: Invalid value for "--input_file": Could not open file: wrong_file.txt: No such file or directory

If input_file is not provided, we use click.prompt to create a prompt window on the command line to allow the user to directly enter text. This prompt will be used in encryption mode. is hidden. The effect is as follows:

> python caesar_script_v2.py --encrypt --key 2
Enter a text: **************
yyy.ukectc.eqo

假设你是一名黑客:想要解密一个用凯撒加密过的密文,但你不知道秘钥是什么。最简单的策略就是用所有可能的秘钥调用解密函数 25 次,阅读解密结果,看看哪个是合理的。但你很聪明,而且也很懒,所以你想让整个过程自动化。确定解密后的 25 个文本哪个最可能是原始文本的方法之一,就是统计所有这些文本中的英文单词的个数。这可以使用 PyEnchant 模块实现:

import click
import enchant
from caesar_encryption import encrypt
@click.command()
@click.option(
'--input_file',
type=click.File('r'),
required=True,
)
@click.option(
'--output_file',
type=click.File('w'),
required=True,
)
def caesar_breaker(input_file, output_file):
cyphertext = input_file.read()
english_dictionnary = enchant.Dict("en_US")
max_number_of_english_words = 0
for key in range(26):
plaintext = encrypt(cyphertext, -key)
number_of_english_words = 0
for word in plaintext.split(' '):
if word and english_dictionnary.check(word):
number_of_english_words += 1
if number_of_english_words > max_number_of_english_words:
max_number_of_english_words = number_of_english_words
best_plaintext = plaintext
best_key = key
click.echo(f'The most likely encryption key is {best_key}. It gives the following plaintext:nn{best_plaintext[:1000]}...')
output_file.write(best_plaintext)
if __name__ == '__main__':
caesar_breaker()

Use click to create the perfect Python command line program

使用进度条

示例中的文本包含10^4个单词,因此该脚本需要大约5秒才能解密。这很正常,因为它需要检查所有25个秘钥,每个秘钥都要检查10^4个单词是否出现在英文字典中。

假设你要解密的文本包括10^5个单词,那么就要花费50秒才能输出结果,用户可能会非常着急。因此我建议这种任务一定要显示进度条。特别是,显示进度条还非常容易实现。下面是个显示进度条的例子:

import click
import enchant
from tqdm import tqdm
from caesar_encryption import encrypt
@click.command()
@click.option(
'--input_file',
type=click.File('r'),
required=True,
)
@click.option(
'--output_file',
type=click.File('w'),
required=True,
)
def caesar_breaker(input_file, output_file):
cyphertext = input_file.read()
english_dictionnary = enchant.Dict("en_US")
best_number_of_english_words = 0
for key in tqdm(range(26)):
plaintext = encrypt(cyphertext, -key)
number_of_english_words = 0
for word in plaintext.split(' '):
if word and english_dictionnary.check(word):
number_of_english_words += 1
if number_of_english_words > best_number_of_english_words:
best_number_of_english_words = number_of_english_words
best_plaintext = plaintext
best_key = key
click.echo(f'The most likely encryption key is {best_key}. It gives the following plaintext:nn{best_plaintext[:1000]}...')
output_file.write(best_plaintext)
if __name__ == '__main__':
caesar_breaker()

这里使用了tqdm库,tqdm.tqdm类可以将任何可迭代对象转化为一个进度条。click也提供了类似的接口来创建进度条(click.progress_bar),但我觉得它不如tqdm好用。

Use click to create the perfect Python command line program

The above is the detailed content of Use click to create the perfect Python command line program. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:51cto.com. If there is any infringement, please contact admin@php.cn delete