Я сделал командную строку на Python. Прежде чем я продолжу и добавлю другие команды, хорошо ли сделана моя программа?
Мне не нравится огромное количество утверждений «если» в нем. Но я не знаю лучшего способа сделать это. (JSON может быть использован позже, поэтому я включил его)
Вот код:
import time
import json
print("Welcome to pyCMD, usern")
time.sleep(0.2)
input("Press enter to startn")
time.sleep(0.2)
def SavetoDisk(data, file):
with open(file, 'w') as outfile:
json.dump(data, outfile)
def ReadfromDisk(file):
with open(file) as json_file:
data = json.load(json_file)
return data
print('Starting command line, use "help . ." for help')
running = True
commandList = "add | sub | mult | div | exp | tetrate | python"
while running:
try:
userInput = input(': ')
tokens = userInput.split()
command = tokens[0]
args = [(token) for token in tokens[1:]]
except: print("unknown input error")
try:
arg1, arg2 = args
except ValueError:
print('Too many or too little args, 2 args required, if you want to not use an arg, use a "."')
if command == "add":
print(float(arg1) + float(arg2))
if command == "sub":
print(float(arg1) - float(arg2))
if command == "mult":
print(float(arg1) * float(arg2)) #math commands
if command == "div":
print(float(arg1) / float(arg2))
if command == "exp":
print(float(arg1) ** float(arg2))
if command == "tetrate":
for x in range(int(arg2)):
arg1 = float(arg1) * float(arg1)
print(arg1)
if command == "python":
exec(open(arg1).read())
if command == "help":
if arg1 == '.':
if arg2 == '.':
print('To see help about a command, type: "help [command] ." for list of commands type: "help command list"')
if arg1 == 'command':
if arg2 == 'list':
print(commandList)
if arg1 == 'add':
print("Add: n Description: Adds 2 numbers n Syntax: add [num1] [num2]")
if arg1 == 'sub':
print("Sub: nDescription: Subtracts 2 numbers n Syntax : sub [num1] [num2]")
if arg1 == 'mult':
print("Mult: nDescription: Multiplies 2 numbers n Syntax : mult [num1] [num2]")
if arg1 == 'div':
print("Div: nDescription: Divides 2 numbers n Syntax : div [num1] [num2]")
if arg1 == 'exp':
print("Exp: nDescription: Raises 1 number by another n Syntax : exp [num1] [num2]")
if arg1 == 'tetrate':
print("Tetrate: nDescription: Tetration n Syntax : tetrate [num1] [num2]")
if arg1 == 'python':
print("Python: nDescription: Runs a python script n Syntax : python [path/to/program.py] .")
2 ответа
Первое, что я замечаю в вашем коде, это то, что он очень длинный глобальный скрипт. Чтобы решить эту проблему, я бы начал с перемещения вашего кода в functions.
Фактические расчеты могут быть очень простыми, например:
def add(arg1, arg2):
print(float(arg1) + float(arg2))
Чтобы затем получить справочную информацию, мы могли бы добавить docstring.
def add(arg1, arg2):
"""
Add:
Description: Adds 2 numbers
Syntax: add [num1] [num2]
"""
print(float(arg1) + float(arg2))
Мы можем получить как строку документации, так и результат операции с Python. Чтобы строка документации была хорошо отформатирована, мы можем использовать textwrap.dedent
.
>>> add("10", "5")
15.0
>>> import textwrap
>>> textwrap.dedent(add.__doc__.strip("n"))
Add:
Description: Adds 2 numbers
Syntax: add [num1] [num2]
Чтобы затем уменьшить количество строк кода, мы можем объединить все эти функции в словарь. И просто проиндексируйте словарь, чтобы получить конкретную функцию.
COMMANDS = {
"add": add,
"sub": sub,
# ...
}
fn = COMMANDS["add"]
fn("10", "5")
15.0
Хотя вы можете создать словарь и самостоятельно выполнять команды, вместо этого вы можете создать подкласс cmd.Cmd
. Вам нужно будет немного изменить функции, чтобы в качестве входных данных использовалась только строка, а префикс do_
к любым командам, доступным в командной строке.
import cmd
class PyCMD(cmd.Cmd):
intro = 'Welcome to pyCMD, usernStarting command line, use "help" for help'
prompt = ": "
def do_add(self, arg):
"""
Add:
Description: Adds 2 numbers
Syntax: add [num1] [num2]
"""
arg1, arg2 = arg.split()
print(float(arg1) + float(arg2))
PyCMD().cmdloop()
Welcome to pyCMD, user
Starting command line, use "help" for help
: help
Documented commands (type help <topic>):
========================================
add help
: help add
Add:
Description: Adds 2 numbers
Syntax: add [num1] [num2]
: add 10 5
15.0
Помощь явно немного сломана. Чтобы исправить это, мы можем изменить строку документации после функции.
class PyCMD(cmd.Cmd):
def do_add(self, arg):
# ...
do_add.__doc__ = textwrap.dedent(do_add.__doc__.rstrip(" ").strip("n"))
Это немного некрасиво, и делать это для каждой функции было бы ужасно. Итак, мы можем создать функцию для этого.
def clean_doc(fn):
fn.__doc__ = textwrap.dedent(fn.__doc__.rstrip(" ").strip("n"))
class PyCMD(cmd.Cmd):
def do_add(self, arg):
# ...
clean_doc(do_add)
Это все еще немного уродливо, поэтому мы можем использовать @
сделать это для нас. Это называется декоратор. Это имеет смысл, потому что мы украшаем то, как __doc__
виден. Обратите внимание, что мы изменили clean_doc
вернуться fn
.
def clean_doc(fn):
fn.__doc__ = textwrap.dedent(fn.__doc__.rstrip(" ").strip("n"))
return fn
class PyCMD(cmd.Cmd):
@clean_doc
def do_add(self, arg):
# ...
Мы можем добавить еще одну функцию, например clean_doc
, но на этот раз сделайте это закрытие если вы хотите легко добавить проверку двух аргументов.
import functools
def command(fn):
@functools.wraps(fn)
def wrapper(self, arg):
args = [token for token in arg.split()]
try:
arg1, arg2 = args
except ValueError:
print('Too many or too little args, 2 args required, if you want to not use an arg, use a "."')
else:
return fn(self, arg1, arg2)
return wrapper
class PyCMD(cmd.Cmd):
@command
@clean_doc
def do_add(self, arg1, arg2):
"""
Add:
Description: Adds 2 numbers
Syntax: add [num1] [num2]
"""
print(float(arg1) + float(arg2))
import cmd
import textwrap
import functools
def command(fn):
@functools.wraps(fn)
def wrapper(self, arg):
try:
arg1, arg2 = arg.split()
except ValueError:
print('Too many or too little args, 2 args required, if you want to not use an arg, use a "."')
else:
return fn(self, arg1, arg2)
return wrapper
def clean_doc(fn):
fn.__doc__ = textwrap.dedent(fn.__doc__.rstrip(" ").strip("n"))
return fn
class PyCMD(cmd.Cmd):
intro = 'Welcome to pyCMD, usernStarting command line, use "help" for help'
prompt = ": "
@command
@clean_doc
def do_add(self, arg1, arg2):
"""
Add:
Description: Adds 2 numbers
Syntax: add [num1] [num2]
"""
print(float(arg1) + float(arg2))
PyCMD().cmdloop()
Welcome to pyCMD, user
Starting command line, use "help" for help
: help
Documented commands (type help <topic>):
========================================
add help
: help add
Add:
Description: Adds 2 numbers
Syntax: add [num1] [num2]
: add 10 5
15.0
Остальные команды оставлены вам для реализации
Отзыв по главному вопросу: уменьшаем если
Позвольте мне начать с того, что Python не поддерживает switch
заявления, которые были бы очень полезны в этом случае. Я бы рекомендовал прочитать это Ответ StackOverflow о том, как получить аналогичный результат (здесь я тоже буду использовать этот подход).
Вы должны обратить внимание на то, что ваши операторы if:
if command == "add":
print(float(arg1) + float(arg2))
запускают код, который следует этому шаблону:
print(arg operand arg)
Отсюда следует вопрос: как это обобщить?
Оказывается, мы можем использовать operator
в наших интересах здесь:
import operator
def operation(a, b, operand):
return operand(a, b)
Затем вы можете сопоставить свои «команды» с разными операндами как таковыми:
def get_operator(x):
return {
'add': operator.add,
'multiply': operator.mul,
...
}[x]
и, наконец, вы можете уменьшить количество «если» с помощью одной строчки кода:
operation(arg1, arg2, get_operator(command))
Следует отметить, что вы также могли написать:
get_operator(command)(arg1, arg2)
Можно утверждать, что один подход лучше другого по разным причинам, связанным с абстракциями, декомпозицией и т. Д.
Общий обзор
Связать
Вместо того, чтобы делать всю эту токенизацию команд самостоятельно, вы должны использовать argparse модуль. Я не собираюсь объяснять здесь, как преобразовать ваш код для использования argparse, но если вы будете следовать руководству в документации, то это должно быть несложно. Это также значительно сократит объем кода, который вы написали для меню справки.
Именование
В Python рекомендуется использовать snake_case
для именования переменных и функций. Итак, функция SavetoDisk
станет save_to_disk
.
Списки и перечисления
Ваш commandList
это не список. Это просто строка, разделенная |
характер. Лучше подумайте об использовании истинного списка и перечисляет а не строки для ваших команд.
commands = [ Command.ADD, Command.MULTIPLY, ...]
Удачи!
Обновление сопоставления с шаблоном Python
Я только что узнал, что предложения по сопоставлению с образцом (PEP 636 и товарищи PEP 634, PEP 635) и только вчера были приняты в разработку 02.08.2021!
Что хорошего в этом ответе, так это разделение внешнего поведения (запрос строки, разделение, проверка количества слов, поддержка
help
command) из внутреннего представления возможных команд. Внутреннее представление идиоматические функции Python 👏. Это широко применимая идея, и она почти всегда проясняет оба аспекта, чем когда они смешаны.— Бени Чернявский-Паскин
Это очень хорошо написанный ответ. Мне нравится, как вы превратили код в красивый расширяемый фреймворк для максимально простого добавления будущих команд. Я тоже люблю декораторов. Я определенно кое-что узнал из этого ответа, хотя считал себя очень свободно владеющим питоном и питоническим дизайном!
— викарджрамун