Бот Python Discord

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

На заднем плане есть 2 файла JSON. Мой бот в основном читает и фильтрует эти файлы JSON и возвращает то, что находит. В java я привык к потокам, которых у меня нет в python, поэтому мне просто пришлось довольствоваться тем, что я знаю, но я чувствую, что некоторые вещи определенно можно сделать чище / короче / лучше /, а я не Я уверен, что все соответствует стандартам Python, поэтому буду благодарен за любые отзывы.


 [ !!             cocktails can be exchanged with -c, ingredients with -i                !! ]
 [ <KW> find cocktails <search criteria1> <search criteria2> ...             Find cocktail. ]
 [ <KW> find ingredients <search criteria1> <search criteria2> ...         Find ingredient. ]
 [ <KW> cocktails                                                       List all cocktails. ]
 [ <KW> ingredients                                                   List all ingredients. ]
 [ <KW> categories                                            List all cocktail categories. ]
 [ <KW> list <category>                                      List all cocktails by category ]
 [ <KW> count cocktails                                                 How many cocktails? ]     
 [ <KW> count ingredients                                             How many ingredients? ]    
 [ <KW> with <ingredient> <ingredient2> ...                      Cocktails with ingredient. ]     
 [ <KW> recipe <cocktail>                                           How to make a cocktail. ]

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


bartender.py

import json

class Bartender:
    with open("recipes.json") as recipes_file:
        recipes = json.load(recipes_file)

    with open("ingredients.json") as ingredients_file:
        ingredients = json.load(ingredients_file)

    default = "This command does not exist or you mistyped something"
    error = "There was a problem with processing the command"

    def handle(self, message):
        command_prefix = message.content.strip().lower()
        answer = self.default
        if self.starts_with_ingredients_prefix(command_prefix):
            answer = self.get_all_ingredients()
        elif self.starts_with_cocktails_prefix(command_prefix):
            answer = self.get_all_cocktails()
        elif command_prefix == "categories":
            answer = self.get_all_categories()
        elif command_prefix.startswith("count"):
            answer = self.get_count(command_prefix.removeprefix("count").strip())
        elif command_prefix.startswith("recipe"):
            answer = self.get_recipe(command_prefix.removeprefix("recipe").strip())
        elif command_prefix.startswith("list"):
            answer = self.get_cocktails_by_category(command_prefix.removeprefix("list").strip())
        elif command_prefix.startswith("find"):
            answer = self.find(command_prefix.removeprefix("find").strip())
        elif command_prefix.startswith("with"):
            answer = self.get_cocktails_with(command_prefix.removeprefix("with").strip())
        return answer

    def get_all_ingredients(self):
        """Returns a string containing all of the ingredients the bot knows."""
        answer = ""
        for key, value in self.ingredients.items():
            answer += f"{key} ({value.get('abv')}%)n"
        return answer

    def get_all_cocktails(self):
        """Returns a string containing the names of all of the cocktails the bot knows."""
        answer = ""
        for cocktail in self.recipes:
            answer += f"{cocktail.get('name')}n"
        return answer

    def get_all_categories(self):
        """Returns a string containing all the cocktail categories the bot knows."""
        answer = ""
        categories_list = []
        for cocktail in self.recipes:
            categories_list.append(cocktail.get("category"))
        # Remove duplicates
        categories_list = list(set(categories_list))
        categories_list.sort(key=str)
        for category in categories_list:
            answer += f"{category}n"
        return answer

    def get_count(self, param):
        """Returns the amount of ingredients or cocktails the bot knows."""
        answer = self.error
        if self.starts_with_ingredients_prefix(param):
            answer = len(self.ingredients)
        elif self.starts_with_cocktails_prefix(param):
            answer = len(self.recipes)

        return answer

    def get_recipe(self, param):
        """Returns the full recipe for the passed cocktail name."""
        answer = f"There is no recipe for a cocktail called {param}. To see all cocktails with a recipe " 
                 f"type '$bt cocktails'"
        for cocktail in self.recipes:
            if param == cocktail.get("name").lower():
                formatted_ingredients = self.get_formatted_ingredients(cocktail.get("ingredients"))
                garnish = self.get_garnisch(cocktail)
                return f"__**{cocktail.get('name')}**__n" 
                       f"**Ingriedients:**n" 
                       f"{formatted_ingredients}" 
                       f"{garnish}" 
                       f"**Preparation:**n" 
                       f"{cocktail.get('preparation')} n"
        return answer

    def get_formatted_ingredients(self, ingredients):
        """Returns a string of ingredients formatted as list for the cocktails including the special ones if it has
        any."""
        formatted_ingredients = ""
        special_ingredients = ""
        for ingredient in ingredients:
            if ingredient.get("special") is not None:
                special_ingredients += f" - {ingredient.get('special')}n"
            else:
                formatted_ingredients += f" - {ingredient.get('amount')} {ingredient.get('unit')} {ingredient.get('ingredient')} "
                if ingredient.get("label") is not None:
                    f"({ingredient.get('label')})"
                formatted_ingredients += "n"

        return formatted_ingredients + special_ingredients

    def get_garnisch(self, cocktail):
        """Returns the garnish for the cocktail if it has one."""
        if cocktail.get("garnish") is not None:
            return f"**Garnish:**n" 
                   f" - {cocktail.get('garnish')} n"
        else:
            return ""

    def get_cocktails_by_category(self, category):
        """Returns all cocktails in the given category."""
        answer = ""
        for cocktail in self.recipes:
            if category == str(cocktail.get("category")).lower():
                answer += f"{cocktail.get('name')}n"

        return answer if len(answer) > 0 else f"There is no category called {category} or it contains no cocktails"

    def starts_with_cocktails_prefix(self, param):
        """Returns true if passed string starts with the cocktails prefix (-c or cocktails)."""
        return param.startswith("-c") or param.startswith("cocktails")

    def remove_cocktails_prefix(self, param):
        """Returns a string with the cocktails prefix (-c or cocktails) removed. If the string does not start with
        the cocktails prefix it will return the original string."""
        if param.startswith("-c"):
            param = param.removeprefix("-c")
        elif param.startswith("cocktails"):
            param = param.removeprefix("cocktails")
        return param

    def starts_with_ingredients_prefix(self, param):
        """Returns true if passed string starts with the ingredient prefix (-i or ingredients)."""
        return param.startswith("-i") or param.startswith("ingredients")

    def remove_ingredients_prefix(self, param):
        """Returns a string with the ingredient prefix (-i or ingredients) removed. If the string does not start with
        the ingredients prefix it will return the original string."""
        if param.startswith("-i"):
            param = param.removeprefix("-i")
        elif param.startswith("ingredients"):
            param = param.removeprefix("ingredients")
        return param

    def find(self, param):
        """Returns all ingredients or cocktails containing the criteria in the parameter separated by commas."""
        answer = ""
        if self.starts_with_cocktails_prefix(param):
            param = self.remove_cocktails_prefix(param)
            for criteria in param.strip().split():
                answer += f"**Criteria: {criteria}**n"
                answer += self.get_cocktails_containing(criteria)
        elif self.starts_with_ingredients_prefix(param):
            param = self.remove_ingredients_prefix(param)
            for criteria in param.strip().split():
                answer += f"**Criteria: {criteria}**n"
                answer += self.get_ingredients_containing(criteria)

        return answer if len(answer) > 0 else "Nothing was found matching your criteria"

    def get_cocktails_containing(self, criteria):
        """Returns all cocktails containing the criteria in its name."""
        answer = ""
        for cocktail in self.recipes:
            if criteria in str(cocktail.get("name")).lower():
                answer += f"{cocktail.get('name')}n"

        return answer if len(answer) > 0 else "Nothing was found matching your criteria"

    def get_ingredients_containing(self, criteria):
        """Returns all ingredients containing the criteria in its name."""
        answer = ""
        for ingredient in self.ingredients.keys():
            if criteria in ingredient.lower():
                answer += f"{ingredient}n"

        return answer if len(answer) > 0 else "Nothing was found matching your criteria"

    def get_cocktails_with(self, param):
        """Returns all cocktails containing the searched for ingredients in the parameter separated by commas."""
        answer = ""
        for ingredient in param.strip().split(","):
            for cocktail in self.recipes:
                cocktail_ingredients = cocktail.get("ingredients")
                answer += self.does_cocktail_contain(cocktail, cocktail_ingredients, ingredient.strip())

        return answer if len(answer) > 0 else "Nothing was found matching your criteria"

    def does_cocktail_contain(self, cocktail, cocktail_ingredients, ingredient):
        """Returns the name of the cocktail if the cocktail contains the searched for ingredient."""
        for cocktail_ingredient in cocktail_ingredients:
            if cocktail_ingredient.get("ingredient") is not None and ingredient in cocktail_ingredient.get(
                    "ingredient").lower():
                return f"{cocktail.get('name')}n"

        return ""


recipes.json

[
  { "name": "Vesper",
    "glass": "martini",
    "category": "Before Dinner Cocktail",
    "ingredients": [
      { "unit": "cl",
        "amount": 6,
        "ingredient": "Gin" },
      { "unit": "cl",
        "amount": 1.5,
        "ingredient": "Vodka" },
      { "unit": "cl",
        "amount": 0.75,
        "ingredient": "Lillet Blonde" },
      { "special": "3 dashes Strawberry syrup" }
    ],
    "garnish": "Lemon twist",
    "preparation": "Shake and strain into a chilled cocktail glass." 
  },
  ...
]

Не все коктейли имеют атрибут стекла, и в коктейле может быть от 0 до n «особых» ингредиентов.


ингредиенты.json

{
  "Absinthe": {
    "abv": 40,
    "taste": null
  },
  "Aperol": {
    "abv": 11,
    "taste": "bitter"
  },
  ...
}

3 ответа
3

get_all_ingredients

Составьте список являются очень питонический и часто в разы быстрее (особенно для операций, которые не требуют больших вычислительных затрат), поэтому вместо

answer = ""
for key, value in self.ingredients.items():
    answer += f"{key} ({value.get('abv')}%)n"

ты должен использовать

answer = "".join(f"{key} ({value.get('abv')}%)n" for key, value in self.ingredients.items())
return answer

или как однострочный

return "".join(f"{key} ({value.get('abv')}%)n" for key, value in self.ingredients.items())

str.join(...) объединяет каждый элемент итерации с помощью разделителя строк (строки, в которой он вызывается) и возвращает объединенную строку.

То, что мы здесь используем, на самом деле не понимание списка, а выражение генератора. Если вам интересно, вы можете узнать больше о Выражения генератора против представлений списков.

То же самое применимо и к get_all_cocktails.


get_all_categories

Как было показано ранее, мы можем заменить

categories_list = []
for cocktail in self.recipes:
    categories_list.append(cocktail.get("category"))

путем понимания списка. Но поскольку нам действительно нужен отсортированный список различных значений, мы можем использовать понимание множества или использовать set() и выражение генератора.

{cocktail.get("category") for cocktail in self.recipes}
# or
set(cocktail.get("category") for cocktail in self.recipes)

Теперь обернем это sorted() который возвращает отсортированный список, и мы получаем categories_list лаконично. Предоставление str поскольку ключевая функция не требуется, поскольку значения уже являются строками.

categories_list = sorted(set(cocktail.get("category") for cocktail in self.recipes))

С использованием str.join() снова сводит весь метод к 2 строкам:

categories_list = sorted(set(cocktail.get("category") for cocktail in self.recipes))
return "".join(f"{category}n" for category in categories_list)

Обратите внимание, что нам не нужно назначать пустую строку в качестве значения по умолчанию, поскольку "".join(...) вернет пустую строку, если в итерации нет элементов.


get_formatted_ingredients

Вы часто сможете заменить

if ingredient.get("special") is not None:

по

if ingredient.get("special"):

Однако это зависит от варианта использования, поскольку выражения не эквивалентны. Второй не сработает для всех ложных значений, а не только None (например, в данном случае пустая строка, которую вы также можете пропустить). Если вам интересно, вы можете узнать больше о Истинные и ложные значения в Python.

Используя моржовый оператор мы можем сделать его более кратким и удалить лишнее get:

if special_ingredient := ingredient.get("special") is not None:
    special_ingredients += f" - {special_ingredient}n"

Я не думаю, что следующий код вообще что-то делает, поскольку он ничего не делает со строкой:

if ingredient.get("label") is not None:
    f"({ingredient.get('label')})"

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

  • 2

    понимание списка […] в целом более эффективный — Это зависит от множества факторов и не всегда так; но здесь эффективность не является проблемой

    — Райндериен

  • Вы также можете рассмотреть возможность использования литерала набора {} а не названный set() конструктор.

    — Райндериен

  • 1

    Ваша рекомендация попробовать .removeprefix("-c").removeprefix("cocktails") глючит. Что делать, если пользователь проходит -ccocktails?

    — Райндериен

  • 1

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

    — рискованный пингвин

  • 1

    Большое спасибо за отличную обратную связь, похоже, у меня много работы: D. Есть ли у вас идеи по улучшению метода ручки? Я не фанат else if, однако, я начал с переключателя, но после определенного момента это стало невозможно.

    — Люцифер Учиха

Экземплярность

Чтение вашего файла выполняется в статической области класса. Его следует переместить в область действия экземпляра класса.

Подсказки по типу

Добавьте некоторые подсказки типа PEP484 в сигнатуры ваших функций.

Тип разумности и словарный суп

После того, как вы прочитали свои данные из JSON, вы не должны оставлять их как словари — создавайте настоящие классы, чтобы ваши данные имели более надежные гарантии правильности, а инструменты статического анализа действительно могли что-то сказать о вашем коде.

Еще одно преимущество этого подхода заключается в том, что ваши новые классы для рецептов и ингредиентов могут иметь методы, работающие с данными, специфичными для этого класса.

Индексирование

Вы не создали никаких индексных словарей для более быстрого и простого поиска на основе условий поиска; вам следует.

Продолжение линии

Для длинных строк лучше заключать их в выражения в скобках, а не добавлять обратную косую черту.

Ошибка: метка

Это утверждение:

f"({ingredient.get('label')})"

ничего не делает, поэтому вы никогда не увидите свой лейбл.

Написание

гарниш -> гарнир

Ингредиенты -> Ингредиенты

Документация ложь

  """Returns all ingredients or cocktails containing the criteria in the parameter separated by commas."""

на самом деле не то, что происходит. Ваш split() разбивается на пробелы.

Статика

Любой метод, который на самом деле не касается self Ссылка не обязательно должна быть методом экземпляра и может извлекать выгоду из статического метода, а иногда и метода класса, если (проще говоря) ссылается на другую статику или конструктор. В реальности @classmethods может ссылаться на любой атрибут объекта класса, что является чуть более подробной информацией, чем я подозреваю, что вам нужно прямо сейчас.

Преимущества правильного использования статических методов включают более легкую тестируемость из-за меньшего количества внутренних зависимостей и более точное представление того, «что на самом деле происходит» для других разработчиков, читающих ваш код.

Также стоит упомянуть (из-за вашего комментария), что публичный / частный и экземпляр / статический — очень разные механизмы. public / private на других языках контролирует видимость извне класса, тогда как instance / static контролирует, виден ли метод в экземпляре или в самом типе класса. Python вообще не имеет четкой концепции публичного / частного, за исключением соглашений, связанных с указанием лидирующего подчеркивания-подразумевающего-частного и двойного подчеркивания-подразумевающего-имени-искажения; я думаю, здесь ни то, ни другое не требуется.

Предложенный

Есть много способов реализовать некоторые из вышеперечисленных, особенно преобразование в классы. Ниже представлен один способ.

import json
from collections import defaultdict
from dataclasses import dataclass
from itertools import chain
from typing import Optional, Iterable, Tuple, Dict, Any, List


@dataclass
class FakeMessage:
    content: str


@dataclass
class Ingredient:
    name: str
    abv: float
    taste: Optional[str]

    @classmethod
    def from_json(cls, fname: str="ingredients.json") -> Iterable['Ingredient']:
        with open(fname) as ingredients_file:
            data = json.load(ingredients_file)

        for name, attributes in data.items():
            yield cls(name=name, **attributes)

    def __str__(self):
        return f'{self.name} ({self.abv}%)'


@dataclass
class RecipeIngredient:
    unit: str
    amount: float
    ingredient: Ingredient
    label: Optional[str] = None

    @classmethod
    def from_dicts(
        cls,
        dicts: Iterable[Dict[str, Any]],
        all_ingredients: Dict[str, Ingredient],
    ) -> Iterable['RecipeIngredient']:
        for data in dicts:
            if 'special' not in data:
                name = data.pop('ingredient')
                yield cls(**data, ingredient=all_ingredients[name.lower()])

    def __str__(self):
        desc = f'{self.amount} {self.unit} {self.ingredient}'
        if self.label is not None:
            desc += f' {self.label}'
        return desc


@dataclass
class Recipe:
    name: str
    glass: str
    category: str
    preparation: str
    ingredients: Tuple[RecipeIngredient, ...]
    special_ingredients: Tuple[str, ...]
    garnish: Optional[str] = None

    @classmethod
    def from_json(
        cls,
        all_ingredients: Dict[str, Ingredient],
        fname: str="recipes.json",
    ) -> Iterable['Recipe']:
        with open(fname) as recipes_file:
            data = json.load(recipes_file)

        for recipe in data:
            ingredient_data = recipe.pop('ingredients')
            specials = tuple(
                ingredient['special']
                for ingredient in ingredient_data
                if 'special' in ingredient
            )
            yield cls(
                **recipe,
                ingredients=tuple(
                    RecipeIngredient.from_dicts(ingredient_data, all_ingredients)
                ),
                special_ingredients=specials,
            )

    @property
    def formatted_ingredients(self) -> str:
        """Returns a string of ingredients formatted as list for the cocktails including the special ones if it has
        any."""
        ingredients = chain(self.ingredients, self.special_ingredients)
        return 'n'.join(
            f' - {ingredient}'
            for ingredient in ingredients
        )

    @property
    def as_markdown(self) -> str:
        desc = (
            f"__**{self.name}**__n"
            f"**Ingredients:**n"
            f"{self.formatted_ingredients}n"
        )

        if self.garnish is not None:
            desc += f' - {self.garnish}n'

        desc += (
            f"**Preparation:**n"
            f"{self.preparation}n"
        )
        return desc


class Bartender:
    ERROR = "There was a problem with processing the command"

    def __init__(self):
        self.ingredients: Dict[str, Ingredient] = {
            i.name.lower(): i for i in Ingredient.from_json()
        }
        self.recipes: Dict[str, Recipe] = {
            r.name.lower(): r for r in Recipe.from_json(self.ingredients)
        }

        self.categories: Dict[str, List[Recipe]] = defaultdict(list)
        for recipe in self.recipes.values():
            self.categories[recipe.category.lower()].append(recipe)

        self.by_ingredient: Dict[str, List[Recipe]] = defaultdict(list)
        for recipe in self.recipes.values():
            for ingredient in recipe.ingredients:
                self.by_ingredient[ingredient.ingredient.name.lower()].append(recipe)

    def handle(self, message: FakeMessage) -> str:
        command_prefix = message.content.strip().lower()

        if self.starts_with_ingredients_prefix(command_prefix):
            return self.get_all_ingredients()
        if self.starts_with_cocktails_prefix(command_prefix):
            return self.get_all_cocktails()
        if command_prefix == "categories":
            return self.get_all_categories()
        if command_prefix.startswith("count"):
            return self.get_count(command_prefix.removeprefix("count").strip())
        if command_prefix.startswith("recipe"):
            return self.get_recipe(command_prefix.removeprefix("recipe").strip())
        if command_prefix.startswith("list"):
            return self.get_cocktails_by_category(command_prefix.removeprefix("list").strip())
        if command_prefix.startswith("find"):
            return self.find(command_prefix.removeprefix("find").strip())
        if command_prefix.startswith("with"):
            return self.get_cocktails_with(command_prefix.removeprefix("with").strip())

        return "This command does not exist or you mistyped something"

    def get_all_ingredients(self) -> str:
        """Returns a string containing all of the ingredients the bot knows."""
        return 'n'.join(str(i) for i in self.ingredients.values())

    def get_all_cocktails(self) -> str:
        """Returns a string containing the names of all of the cocktails the bot knows."""
        return 'n'.join(r.name for r in self.recipes.values())

    def get_all_categories(self) -> str:
        """Returns a string containing all the cocktail categories the bot knows."""
        categories = sorted({
            recipe.category
            for recipe in self.recipes.values()
        })
        return 'n'.join(categories)

    def get_count(self, param: str) -> str:
        """Returns the amount of ingredients or cocktails the bot knows."""
        if self.starts_with_ingredients_prefix(param):
            sequence = self.ingredients
        elif self.starts_with_cocktails_prefix(param):
            sequence = self.recipes
        else:
            return self.ERROR

        return str(len(sequence))

    def get_recipe(self, param: str) -> str:
        """Returns the full recipe for the passed cocktail name."""
        recipe = self.recipes.get(param)
        if recipe is None:
            return (
                f"There is no recipe for a cocktail called {param}. To see all "
                f"cocktails with a recipe type '$bt cocktails'"
            )
        return recipe.as_markdown

    def get_cocktails_by_category(self, category: str) -> str:
        """Returns all cocktails in the given category."""
        recipes = self.categories.get(category)
        if not recipes:
            return f"There is no category called {category} or it contains no cocktails"

        return 'n'.join(r.name for r in recipes)

    @staticmethod
    def starts_with_cocktails_prefix(param: str) -> bool:
        """Returns true if passed string starts with the cocktails prefix (-c or cocktails)."""
        return param.startswith("-c") or param.startswith("cocktails")

    @staticmethod
    def remove_cocktails_prefix(param: str) -> str:
        """Returns a string with the cocktails prefix (-c or cocktails) removed. If the string does not start with
        the cocktails prefix it will return the original string."""
        if param.startswith("-c"):
            return param.removeprefix("-c")
        if param.startswith("cocktails"):
            return param.removeprefix("cocktails")
        return param

    @staticmethod
    def starts_with_ingredients_prefix(param: str) -> bool:
        """Returns true if passed string starts with the ingredient prefix (-i or ingredients)."""
        return param.startswith("-i") or param.startswith("ingredients")

    @staticmethod
    def remove_ingredients_prefix(param: str) -> str:
        """Returns a string with the ingredient prefix (-i or ingredients) removed. If the string does not start with
        the ingredients prefix it will return the original string."""
        if param.startswith("-i"):
            return param.removeprefix("-i")
        if param.startswith("ingredients"):
            return param.removeprefix("ingredients")
        return param

    def find(self, param: str) -> str:
        """Returns all ingredients or cocktails containing the criteria in the parameter separated by commas."""
        answer = ""
        if self.starts_with_cocktails_prefix(param):
            param = self.remove_cocktails_prefix(param)
            for criteria in param.strip().split():
                answer += f"**Criteria: {criteria}**n"
                answer += self.get_cocktails_containing(criteria)
            return answer

        if self.starts_with_ingredients_prefix(param):
            param = self.remove_ingredients_prefix(param)
            for criteria in param.strip().split():
                answer += f"**Criteria: {criteria}**n"
                answer += self.get_ingredients_containing(criteria)
            return answer

        return self.ERROR

    def get_cocktails_containing(self, criteria: str) -> str:
        """Returns all cocktails containing the criteria in its name."""
        answer = ""
        for name, recipe in self.recipes.items():
            if criteria in name:
                answer += f"{recipe.name}n"

        if answer:
            return answer
        return "Nothing was found matching your criteria"

    def get_ingredients_containing(self, criteria: str) -> str:
        """Returns all ingredients containing the criteria in its name."""
        answer = ""
        for name, ingredient in self.ingredients.items():
            if criteria in name:
                answer += f"{ingredient.name}n"

        if answer:
            return answer
        return "Nothing was found matching your criteria"

    def get_cocktails_with(self, param: str) -> str:
        """Returns all cocktails containing the searched for ingredients in the parameter separated by commas."""
        answer="n".join(
            recipe.name
            for ingredient in param.strip().split(",")
            for recipe in self.by_ingredient.get(ingredient, ())
        )

        if answer:
            return answer
        return "Nothing was found matching your criteria"


def test():
    bar = Bartender()
    while True:
        try:
            cmd = input('> ')
        except KeyboardInterrupt:
            break

        print(bar.handle(FakeMessage(cmd)))
        print()

test()

  • Большое спасибо за отличный отзыв: D Есть ли у вас какие-нибудь идеи по улучшению метода ручки? Я не фанат большой эльфийки. hОднако я начал с переключателя, но после определенного момента это стало невозможным.

    — Люцифер Учиха

  • У меня есть 2 вопроса после дальнейшего рассмотрения вашего предложения. Для чего нужен FakeMessage? Я не могу обернуться вокруг него, потому что он используется только в ручке. Другой вопрос: почему некоторые методы статичны? В java я обычно делаю все методы закрытыми, кроме дескриптора, но это невозможно, поскольку я

    — Люцифер Учиха

  • 2

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

    — Хакаишин

  • @Hakaishin Это «мнение» по какой-то причине — я рад поделиться дополнительными обоснованиями для любых пунктов, которые, по вашему мнению, в них необходимы.

    — Райндериен

  • @LuciferUchiha FakeMessage это то, что я использовал, чтобы имитировать настоящий Discord, так что тесту это не нужно. В фактическом коде будет тип из библиотеки Discord, на который вы захотите сослаться.

    — Райндериен

Вы можете заменить некоторые циклы for на понимание списков. Я думаю, что понимание списков обычно предпочтительнее и часто быстрее. Я также:

  • добавлено значение по умолчанию для команды get на случай, если ваш JSON по какой-то причине имеет неправильный формат или отсутствует ABV, и
  • заменил n в конце каждой строки с методом соединения.
def get_all_ingredients(self):
    """Returns a string containing all of the ingredients the bot knows."""
    answer = ""
    for key, value in self.ingredients.items():
        answer += f"{key} ({value.get('abv')}%)n"
    return answer

def get_all_ingredients(self):
    answers = [f'{key} ({value.get("drink_info", "-")}%)' for key, value in self.ingredients.items()]
    return 'n'.join(answers)

'n'.join(answers) отличается от реализации OP, поскольку не включает завершающую новую строку. Может быть, а может и не быть проблемой, в зависимости от варианта использования.

— рискованный пингвин

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

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