Лучшее решение для передачи аргументов функции в словаре

Я только начал изучать Python, в основном по некоторым видео на YouTube и другим онлайн-руководствам.

Теперь я хочу создать словарь вызываемых объектов (действий), запрашивающих ввод пользователя (горячую клавишу), но пользователь также должен иметь возможность передавать больше аргументов действиям.

Моя проблема в том, что мое текущее решение не очень элегантный и чувствует себя вроде как неправильный мне.

Действительно ли это единственный способ добиться того, чего я хочу, или есть лучшая / правильная реализация?

Это мой пример кода:

from collections import OrderedDict

# functions that prints all the possible hotkys and a description

def add_action(actions: dict, hotkey: str, action: object, name: str):
    actions[hotkey.lower()] = action
    actions[hotkey.upper()] = action
    print('- {}: {}'.format(hotkey, name))

# all the functions that we want to execute

def do_a(name: str):
    print('Hello ' + name + ' this is A')


def do_b():
    print('This is B')


def do_c():
    print('This is C')

# function to create a dictionary with all the callables and the corresponding hotkyes

def get_available_actions():

    actions = OrderedDict()

    add_action(actions, hotkey='a', action=do_a, name="Action A will also print your name (use like a john")
    add_action(actions, hotkey='b', action=do_b, name="Action B")
    add_action(actions, hotkey='c', action=do_c, name="Action C")

    return actions

# the main function that is executed to ask the user what to do

def choose_action():
    action = None

    while not action:
        available_actions = get_available_actions()

        # this is the current solution to get arguments
        # first get the whole input
        command = input('nWhat do you want to do? ')

        action_input = None
        arg = None

        # check if the user typed in more than one string spereated by a whitespace and split it
        if ' ' in command:
            action_input, arg = command.split(' ')
        else:
            action_input = command

        # now get the action based on the hotkey, which should have been the first argument
        # using get here to avoid, that the input is a "hotkey" that we don't have in our dictionary
        action = available_actions.get(action_input)

        if action:
            # and if we have an argument, pass it to the action
            if arg:
                action(arg.strip())
            else:
                action()
        else:
            print('Not found, choose again')


choose_action()

```

2 ответа
2

Так здорово, что вы начинаете изучать Python! Это забавный и легкий язык для начала работы. Я буду эхом м-алорда и рекомендую вам использовать какую-нибудь библиотеку, специально предназначенную для этого, так как есть много подводных камней.

Однако, если вы хотите узнать, как улучшить код, продолжайте читать.


Общая обратная связь

Code Tells You How, Comments Tell You Why?

Вы пытались задокументировать свой код с помощью комментариев, и это здорово! Однако комментарии, использующие # в основном предназначены для других программистов и обычно зарезервированы для объяснения кратких частей кода. Обычный способ документ ваш код, предназначенный для пользователя, с PEP 257 — Соглашения о строках документов.

Как указано в заголовке комментариев (#) следует использовать редко. Во-первых, вы должны сначала постараться сделать свой код как можно более простым для понимания, не полагаясь на комментарии как на костыль. Только в том месте, где невозможно сделать код более понятным, можно начинать добавлять комментарии.

Ваш код уже достаточно ясен, поэтому я бы удалил все ваши комментарии и заменил их на строки документации.


upper lower

Этот

actions[hotkey.lower()] = action
actions[hotkey.upper()] = action

Можно заменить, используя lower на входе.

f-strings

f-строки — это новый способ формирования строк в Python, и вы обычно должны их использовать. Например

print('- {}: {}'.format(hotkey, name))

становится

print(f'- {hotkey}: {name}')

Что легче читать.

business logic and user interface

Бизнес-логика или логика предметной области — это та часть программы, которая кодирует реальные бизнес-правила, определяющие, как данные могут быть созданы, сохранены и изменены. Важно разделить то, как вы ручка ваши данные (бизнес-логика) от того, как вы настоящее время это пользователю (пользовательский интерфейс).

Так что это

def do_a(name: str):
    print('Hello ' + name + ' this is A')

Действительно должно быть это

def do_a(name: str):
    return 'Hello ' + name + ' this is A'

Обратите внимание, что это позволяет нам изменить способ представления результатов do_a, может быть, мы хотим что-то украсить, добавить или удалить перед тем, как представить это пользователю.


actions = OrderedDict()

Я бы предпочел просто использовать список вместо OrderedDict, но мы еще вернемся к этому.

add_action(actions, hotkey='a', action=do_a, name="Action A will also print your name (use like a john")
add_action(actions, hotkey='b', action=do_b, name="Action B")
add_action(actions, hotkey='c', action=do_c, name="Action C")

Здесь вы смешиваете две концепции, понимаете, какие? nameэто не просто название? Ты используешь name как имя для B и C, но для A вы также используете его как описание. Было бы лучше разделить эти два.

Во-вторых, обратите внимание, что ваши имена — это просто горячие клавиши с заглавной буквы. Так что вместо этого это могло быть

`f'Action {hotkey.upper()}'`

Снова используя эти изящные ф-струны


if __name__ == "__main__":

Поместите те части вашего кода, которые требуют выполнения, за if __name__ == "__main__": сторожить. Таким образом, вы можете импортировать этот модуль python из других мест, если хотите, а защита предотвращает случайный запуск основного кода при каждом импорте.

Так что это

# the main function that is executed to ask the user what to do

def choose_action():
    action = None
.
.
.

становится

def main():
"""Summary of the function goes here"""
.
.
.

`if __name__ == "__main__":
    main()

nitpicking

Используйте подходящий линтер / редактор. Правильный линтер, вероятно, предупредит вас, что
action_input = None никогда не называется. Вы понимаете почему?

    action_input = None
    if ' ' in command:
        action_input, arg = command.split(' ')
    else:
        action_input = command

У вас также были небольшие проблемы с промежутками между вашими функциями, поэтому я подозреваю, что вы могли бы использовать лучший редактор =)

user interface

Мне очень нравится ваш пользовательский интерфейс, в нем в основном ясно, что вы хотите, чтобы ваши пользователи делали. Я бы порекомендовал более четкое название

'nWhat do you want to do? '

В лучшем случае расплывчато ^^


Предложение

  • Реализованы все предложения выше.

  • Используйте класс, чтобы отслеживать, какие функции мы хотим вызывать (разрешение каждой функции плохо с точки зрения безопасности).

  • Мы храним разрешенные действия как namedtuple.

  • Добавляя функции, мы убеждаемся, что они вызываемый.

  • Добавлены опции для более чем одного аргумента. Это делается *args

  • Печать оставляется на усмотрение __str__ часть класса.

  • Части кода переписаны в соответствии с новым моржовый оператор :=

  • Части кода переписаны, чтобы было меньше точки выхода. Обратите внимание, что я не пытаюсь строго следовать однократный вход / однократный выезд мантра, как это может быть вреден при некоторых обстоятельствах. Однако иногда из-за этого намерение кода яснее.

    перед

    if hotkey.lower() in ACTIONS.hotkeys:
        print(ACTIONS.call_function_by_hotkey(hotkey, args))
    else: 
        print("Not found, choose again")
    

    после

    print(
        ACTIONS.call_function_by_hotkey(hotkey, args)
        if hotkey.lower() in ACTIONS.hotkeys
        else "Not found, choose again"
    )
    

    Во втором случае на первом месте стоит самая важная часть: print. Затем применяется условное выражение. Оба стиля хороши, просто будьте комфортны с обоими.

code

from collections import namedtuple


def do_a(name, bobby=""):
    string = f"Hello {name} this is A"
    return string if not bobby else f"{string}nHello {bobby} this is {name}"


def do_b():
    return "This is B"


def do_c():
    return "This is C"


LINEWIDTH = 79
LINEBREAK = "n" + "=" * LINEWIDTH + "n"


class Actions:

    Action = namedtuple("Action", ["hotkey", "name", "function", "description"])

    def __init__(self):
        self.hotkeys = dict()
        self.functions = []

    def add(self, hotkey, function, description, name=None):
        if not self.is_function(function):
            raise NameError("Function " + function + "() is not defined")
        name = "Action " + hotkey.upper() if name is None else name
        self.hotkeys[hotkey] = self.Action(
            hotkey, name, function, description.capitalize()
        )
        self.functions.append(function)

    def is_function(self, function):
        try:
            if eval("callable(" + function + ")"):
                return True
        except NameError:
            return False
        except SyntaxError:
            return False

    def function_2_string(self, function, args, strings=True):
        return function + (
            "()"
            if len(args) == 0
            else "("
            + ", ".join(args if not strings else ["'" + str(arg) + "'" for arg in args])
            + ")"
        )

    def call_function_by_hotkey(self, hotkey, args):
        if function := self.hotkeys[hotkey].function if hotkey in self.hotkeys else "":
            return eval(self.function_2_string(function, args))
        raise NameError("Hotkey " + hotkey + " is not defined")

    def __str__(self):
        string = LINEBREAK + "ACTIONS".center(LINEWIDTH) + LINEBREAK
        action_strings = []
        for hotkey, name, _, description in self.hotkeys.values():
            action_strings.append(f"- {hotkey}: {name}")
            if description:
                action_strings.append("  -- " + description)
        return string + "n".join(action_strings) + LINEBREAK


EXIT_ = set("", "exit", "return", "end", "break") 
ACTIONS = Actions()
ACTIONS.add(
    hotkey="a", function="do_a", description="args: (str) name n optional: (str) bobby"
)
ACTIONS.add(hotkey="b", function="do_b", description="")
ACTIONS.add(hotkey="c", function="do_c", description="")


def main():
    title_txt = str(ACTIONS)
    title_txt += "nPick an hotkey from the list above to perform that actionn"
    while (command := input(title_txt + "> ").lower()) not in EXIT_:

        if " " in command:
            hotkey, *args = command.split(" ")
        else:
            hotkey, args = command, []

        print(
            ACTIONS.call_function_by_hotkey(hotkey, args)
            if hotkey in ACTIONS.hotkeys
            else "Not found, choose again"
        )


if __name__ == "__main__":
    main()

    Если вы не пытаетесь изобретать велосипед, вам нужно использовать некоторую библиотеку, которая позволяет легко создавать интерфейс командной строки. Например, щелкнуть.

      Добавить комментарий

      Ваш адрес email не будет опубликован. Обязательные поля помечены *