Как стать автором
Обновить
1
0

Python-разработчик

Отправить сообщение
Так как статья рассчитана на новичков, поделюсь аналогичной программой для командной строки:
import re, argparse
from os import path

parser = argparse.ArgumentParser(description='Count words in text file.')
parser.add_argument('path', action='store', type=str, help='path to a text file')
args = parser.parse_args()

if path.exists(args.path) and path.isfile(args.path):
    with open(args.path, 'r') as f:
        text = f.read()
else:
    exit(f'Path "{args.path}" does not exist or a directory.')

if not text:
    exit('File is empty.')

words = [word.lower() for word in re.split(r'[^\w-]+', text) if word]
print('Words: ', len(words))
print('Unique words: ', len(set(words)))

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

Автор FastAPI уже этим занимается. :)
Остается пожелать ему удачи и терпения.

позволяет использовать функцию для определения пути

Тут тоже есть свои неудобства, которые описаны в упоминаемой вами документации.


instance — In most cases, this object will not have been saved to the database yet, so if it uses the default AutoField, it might not yet have a value for its primary key field.

То есть, на момент вызова функции переданной в upload_to, instance еще не будет сохранен в базу, а значит, id у него будет равен None.


На SO есть несколько решений этой проблемы особенности, надеюсь, другие джангисты поделятся популярным самым решением.

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

И правильно сделали, как мне кажется.


Сильно уж они все упрощают
import re

def decoded_rle_value_len(value):
    pairs = re.findall(r'(?P<letter>[A-Z])(?P<count>\d*)', value)
    return sum(int(pair[1]) if pair[1] else 1 for pair in pairs)

result = decoded_rle_value_len('A15BA5')
print(result)

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

Вот это решает проблему:


from collections import Counter
from operator import itemgetter

a = [0, 0, 10, 10]
counter = Counter(a)
result = max(counter.items(), key=itemgetter(1, 0))[0]
print(result)

Но оказалось, что решение в посте работает прилично быстрее.

Пользуюсь dynaconf
Умеет в большинство популярных форматов, есть интеграции с Джангой и Флаской, умеет в Vault.

Подобные "упрощения" не очень то и упрощают чтение кода. :)

Круто, что даже без использования numpy при помощи numba можно прилично ускорить работу программы.


Например, если на код автора просто навесить декоратор @jit — программа отработает за 2.528 на моем железе.


Если убрать isinstance — уже будет 1.7.


Что не может не радовать. :)


Все-таки, если уже считаем в Питоне, то берем для этого инструменты, которые для него придумали.

Если бы проблема была и для Windows — было бы куда больше шума, мне кажется.
Попробовал у себя: Windows 7, Pycharm 2019.3, русская раскладка. Хоткеи из тикета работают точно так же, как и на английской раскладке.

Перенес C# реализацию автора на Python 3.7+:


from __future__ import annotations

import asyncio
from dataclasses import dataclass, field
from typing import Generic, Sequence, TypeVar

from aiohttp import ClientSession

T = TypeVar("T")

@dataclass
class Comment:
    id: int
    title: str

    def __repr__(self):
        return f"{self.id} - {self.title}"

@dataclass
class Tree(Generic[T]):
    value: T
    children: Sequence[Tree] = field(default_factory=list)

    def print(self, indentation: str = "") -> None:
        print(f"{indentation}{self.value}")

        for child in self.children:
            child.print(indentation + "\t")

async def get_comment(client: ClientSession, id: int) -> Comment:
    async with client.get(f"https://jsonplaceholder.typicode.com/todos/{id}") as resp:
        raw_comment = await resp.json()
        return Comment(id=raw_comment["id"], title=raw_comment["title"])

async def get_comments_tree(client: ClientSession, tree: Tree[int]):
    children = [get_comments_tree(client, child) for child in tree.children]
    value = await get_comment(client, tree.value)
    chilren_results = await asyncio.gather(*children)
    return Tree[Comment](value, chilren_results)

async def main():
    async with ClientSession() as client:
        tree = Tree(1, children=[Tree(2), Tree(3, children=[Tree(4), Tree(5)])])
        tree.print()

        comment_tree = await get_comments_tree(client, tree)
        comment_tree.print()

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

pyright даже умеет за дженериками следить. А вот mypy и PyCharm почему-то не смогли меня наругать, когда при создании дерева я попытался одной из нод передать в качестве children НЕ Sequence[Tree].


Если кто знает как обучить другие тайпчекеры понимать дженерики — поделитесь, пожалуйста. :)
Попробовать можно на этом:


tree = Tree(1, children=[Tree(2), Tree(3, children=[Tree(4), Tree(5, children='i am not a sequence of trees')])])

Из приятностей еще можно отметить, что если вам понадобится удалить приложение — не придется ходить по каждому сервису и чистить все руками.


chalice delete и все что оно породило — само и удалит.

Весь третий шаг можно опустить, если воспользоваться официальной библиотекой под названием Chalice.


Всё приложение можно уместить в 20 строк кода (у меня не Twilio, но суть та же):


from os import environ

from chalice import Chalice, Cron
from httpx import Client
from httpx.exceptions import HTTPError

app = Chalice(app_name="app")
http = Client()

@app.schedule(Cron(30, 1, "*", "*", "?", "*"))
def send_greetings_periodic(event):
    try:
        http.post(
            environ["DISCORD_WEBHOOK"], json={"content": "Hello!"}
        ).raise_for_status()
    except HTTPError as ex:
        app.log(ex)

А сборкой приложения, настройкой AWS и отправкой туда всего нужного займется CLI, который идет в комплекте.


Зависимости складываем в <app_root>/requirements.txt.


Самое главное — добавить креды AWS в ~/.aws, добавить токен Twilio (в моем случае) URL для Discord, куда будет отправляться приветствие, в environment_variables из файла <app_root>/.chalice/config.json:


{
  ...
  "environment_variables": {
    "DISCORD_WEBHOOK": "definitely_secret"
  },
  ...
}

И сделать chalice deploy.


Если всё сделали правильно — вы восхитительны. :)


(venv) D:\Projects\Python\discord-auto-greeter>chalice deploy
Creating deployment package.
Updating policy for IAM role: discord-auto-greeter-dev
Updating lambda function: discord-auto-greeter-dev-send_greetings_periodic
Resources deployed:
  - Lambda ARN: arn:aws:lambda:probably_not_secret:discord-auto-greeter-dev-send_greetings_periodic

Невероятно много времени экономит.

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

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

Рекомендую попробовать.
Это, наверное, сильно зависит от количества «налайканных» треков. Я вот месяца два пользуюсь YouTube Music, не на постоянной основе, но как минимум час в день музыка играет. Треки лайкаю. И знаете что? В рекомендациях все те же 10 исполнителей. С теми же песнями. От них уже просто тошнит. Я не смог понять как мне получать новую музыку в YouTube Music.

C Яндекс.Музыкой же всё наоборот. Импортировал свой плейлист из 2 000 песен, много лайкал и ежедневный плейлист очень радует.

Слушаю предпочтительно рок, иногда подмешивая разное.

Судя по документации, можно сделать либо с ограниченным количеством аргументов (в данном случае — 3 аргумента; определит из длины кортежа type):


import click

@click.command()
@click.option('--userdata', type=(str, str, int), help='User data represented as "FirstName LastName Age"')
def greet(userdata):
    print('Hello, user {} {}. You are {} y.o.'.format(*userdata))

if __name__ == '__main__':
    greet()

Что можно использовать как:
python app.py --userdata Vasiliy Petrenko 42


Если нужно передавать несколько одинаковых параметров — можно делать так:


import click

@click.command()
@click.option('--name', type=str, help='A name to greet', multiple=True)
def greet(name):
    for n in name:
        print(f'Hello, {n}')

if __name__ == '__main__':
    greet()

И использовать как:
python app.py --name Vasya --name Petya


Указывать неограниченное количество аргументов для Option (например, python app.py --names Vasya Petya Tolya) — нельзя.

Тоже очень нравится click. Особенно приятно работать с путями. Сразу при указании типа аргумента можно провести его валидацию, например:
import click


@click.command()
@click.option(
    "--count",
    default=1,
    type=click.IntRange(min=1, max=100),
    help="Number of files to generate.",
)
@click.option(
    "--output",
    type=click.Path(exists=True, file_okay=False, writable=True),
    help="Output directory script will write files to.",
)
def file_generator(count, output):
    for i in range(count):
        with open(f"{i}.txt", "w") as f:
            f.write(f"Dummy data {i}")


if __name__ == "__main__":
    file_generator()


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

P. S. Да, во время генерации самих файлов проверки все еще нужны, но это уже другая история. :)

P. P. S. Форматировался код при помощи black
Netis WF2411E, родная прошивка, через веб-интерфейс позволил установить SSID в виде emoji без каких-либо проблем. С телефона посмотрел, emoji видно. Выглядит забавно. :)

Установить пароль в виде emoji не позволил, сработала валидация.
Vibora сейчас трогать — грешно. Автор обещает большие изменения, будем надеяться и ждать, но на данный момент, последний коммит — 18 июля. Проблем куча. В продакшн такое не потащишь.

Вот uvicorn / starlette и их друзья — интересные кандидаты. На их основе множество фреймворков наплодилось уже.
В ответ на комментарий blind_oracle:

Не согласен.

Без углублений, которые делал автор, всё можно свести к aiohttp + uvloop и async/await синтаксису. Работается с этим крайне просто. :)

Go, конечно, хорош. Мне в первую очередь было интересно: «А как бы показал себя Go в задачах автора?», но когда всё налажено для Питона — переезжать не хочется.

Для создания простых API в Lambda на Питоне пользуюсь Chalice, очень упрощает жизнь и избавляет от рутины. Напоминает обычное приложение на Фласке. :)

Информация

В рейтинге
Не участвует
Откуда
Украина
Зарегистрирован
Активность