Pull to refresh
27
0.9

Программист

Send message

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

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

Я тоже как раз об этом думал — было бы неплохо сделать два интерфейса:


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

Да, это и есть самый здравый подход. Свобода выбрать и тот и этот способ.

Ну почему, вон те же Apple — копируешь в буфер щипком тремя пальцами к себе, вставляешь щипком от себя; фото сделанное телефоном, может сразу же появится на ПК и т.д.
Другое дело что "компьютерные динозавры" этим не пользуются, им файлы подавай.

О, я был уверен что такую программу ктото уже написал, просто поискать руки не доходили! Щас то я свой архив пропесочу! :)


P.S. в репозитории так много Issues вследствие популярности, или вам не хватает времени их закрывать?

IMHO файл это просто абстракция — а людям то нужны картинки, музыка и видео, которые несут им впечатления, а вовсе не "файлы".
Конечно, многим людям нравятся и носители информации сами по себе — пластинки, диски и, в случае "гиков", — файлы. Но все таки контент важнее носителя (исключение — когда сам носитель уже является музейной ценностью).


Условный Apple выкинул файлы из интерфейса (под капотом они никуда не делись), и предъявляет людям контент, а не носитель, и это и делает интерфейс удобным и доступным даже трехлетним детям.


Конечно же, у конечного юзера становится меньше "контроля" над "своей" информацией, но, как много раз доказано историей, для массовой аудитории удобство приложения всегда побеждает "контролируемость". Это противоречие из разряда "хранить золото в огороде или деньги в банке?". Большинство хранит деньги в банке, а фото в гугл-диске. Да и "деньги" это тоже абстракция. По хорошему надо бы хранить соль, картошку и спички, а не бумажки с президентами.


Надежность "облачного" хранения тоже страдает, но с другой стороны, затем пользователи и платят инженерам, чтобы они занимались надежностью, безопасностью и т.п. Потому что, согласитесь, не могут все до единого граждане заниматся безопасностью своих файлов (а так же выращивать пшеницу, делать одежду и т.п.), большинству людей например физическая безопасность поважнее сохранности файлов будет, и то на 90% она отдана на откуп государству.


Удивление автора насчет "новое против качественного" объясняется так же — большинству нужны "впечатления" а новая информация обычно впечатляет больше, чем качественная, но читанная-перечитанная. Именно поэтому обычных пользователей и не пугает так сильно риск потери файлов. Всегда можно новых накачать, и да и получше старых. А если уж файлы были такими ценными, то обычно они в интернете валяются в великом множестве.


Мне конечно же приведут в пример личные ценности, вроде архива семейных фотографий. И я с этими исключениями согласен. Я сам храню свой фотоархив на файл-сервере с рейдом + внешний USB диск в компьютере в другой стране.


Но с другой стороны, я чувствую, что не так уж и важен этот фото-архив. Может быть разок или два на пенсии просмотрю его (если доживу), а на его хранение, сортирование и копирование уходят драгоценные часы и дни моей жизни. Уж не лучше ли делать, как современные домохозяйки — сфоткал чадо — отправил в семейный чат, тети-дяти поохали, поставили смайликов — все испытали эмоции — всем хорошо. Нафига потом эти фото хранить. Прийдут же новые ситуации, новые дети-внуки. Живая жизнь всегда подкинет контента так сказать. И всегда нового.


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


Основная моя мысль вот какая — чем дольше живу, тем больше понимаю, что собирая "нужные и важные" файлы, я на самом деле похож на мою бабушку, которая собирала всю жизнь "нужные и важные" тряпки, крупы и нитки. Вот и я, сколько не смотрю на свои фото из Гималаев, и близко нет того чувства, что было в реальных горах, только жалкий отблеск.


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


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

Это вы немецкой литературы не читали. Немцы как завернут предложение на пару сотен слов, пока до конца дочитаешь, уже забываешь о чем оно.

А с третьей, когда необходимо обработать или удалить парочку сотен тысяч файлов в конкретной директории, то только find и спасает, ибо он применяет -delete (-exec) в цикле по мере нахождения файлов, а все остальные команды (типа rm dir/*, some_command dir/*, etc) вначале пытаются распаковать список аргументов, на чем благополучно зависают.

А выход один — линукс. Жалко, что в корпоративных условиях этот выход часто невозможен.

А я думаю, что депутаты и приближенные первыми себе закажут модемы. Ведь это "Модно, сильно, молодёжно!" да и чебурнет не грозит тогда.
А запеленговать модем проблематично ибо используется ФАР и в стороны "светить" будет очень слабо.

До этих ваших GPS так и ориентировались, и самолеты и автопилоты ракет, начиная с Фау1, и даже статья на Хабре была, где описывалась система навигации Авто из 80х с гироскопами.
Ошибка навигации в закрытой системе накапливается конечно, так что иногда необходимо корректировать, это уже решалось по разному.

TLDR:
Есть митохондрии, из-за них вы можете болеть. А можете и не болеть. Иногда можно болеть и внезапно вылечиться. А иногда внезапно заболеть.
Спрогнозировать ничего невозможно, но таблетки купите, на всякий случай.

Лучший рассказ, что я читал на Хабре.
Я бы даже эту расу полностью в мир идей поселил, без физической оболочки.

Исходная задача:


Допустим у нас есть дерево идентификаторов комментариев

Я описал узел дерева, который сразу же в себе содержит ID комментария И дочерние узлы.


class IdNode:
    id: int
    children: list

Далее:


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

class MsgNode:
    message: str   # вместо ID - комментарий
    children: list

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


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

С опозданием на десять дней, но проверил — действительно asyncio.gather корректно собирает задачи из вложенного asyncio.gather и все просто работает асинхронно.
Вот полный код:


#!/usr/bin/env python
from dataclasses import dataclass, field
import aiohttp
import asyncio

@dataclass
class IdNode:
    id: int
    children: list = field(default_factory=list)

@dataclass
class MsgNode:
    message: str
    children: list

    def __str__(self, indentation=""):  # из комментария @Senpos ниже
        message = f"{indentation}{self.message}"
        children_messages = [child.__str__(indentation + '\t') for child in self.children]
        return '\n'.join([message, *children_messages])

async def get_comment_by_id(x, session):
    r = await session.get(f"https://jsonplaceholder.typicode.com/todos/{x}")
    data = await r.json()
    print('request', x, 'finished')
    return data['title']

async def map_tree(node, session):
    message, children = await asyncio.gather(
        get_comment_by_id(node.id, session),
        asyncio.gather(*[map_tree(child, session) for child in node.children])
    )
    return MsgNode(message, children)

async def main():
    async with aiohttp.ClientSession() as session:
        message_tree = await map_tree(
            node=IdNode(1, [IdNode(2), IdNode(3, [IdNode(4), IdNode(5)])]),
            session=session
        )
        print('\n', message_tree)

asyncio.run(main())

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

Я добавил печать результатов в get_comment :


...
raw_comment = await resp.json()
print(f"request {id} finished: {raw_comment['title']}")

и в выводе сразу видно, что код не совсем асинхронный, вызовы к back-end асинхронны только для детей одного узла, но не всех узлов одновременно:


request 1 finished: delectus aut autem
request 2 finished: quis ut nam facilis et officia qui
request 3 finished: fugiat veniam minus
request 5 finished: laboriosam mollitia et enim quasi adipisci quia provident illum
request 4 finished: et porro tempora

(последние два вызова могут поменятся местами по времени, но 1..3 — никогда и это будет видно более ясно, если сделать дерево побольше)

Попытался решить задачу на питоне и наконец-то вроде даже понял, как работает asyncio.


Исходные структуры данных:


from dataclasses import dataclass

@dataclass
class IdNode:
    """tree for IDs"""
    id: int
    children: list

@dataclass
class MsgNode:
    """tree for messages"""
    message: str
    children: list

# initial tree with IDs
tree = IdNode(1, [
    IdNode(2, []),
    IdNode(3, [
        IdNode(4, []),
        IdNode(5, [])
    ])
])

print(tree)

Удаленная база сообщений (обращаюсь к ней через typicode, как и автор статьи) :


{
  "messages": [
    {
      "id": 1,
      "message": "1:Оригинальный комментарий"
    },
    {
      "id": 2,
      "message": "2:Ответ на комментарий 1"
    },
    {
      "id": 3,
      "message": "3:Ответ на комментарий 2"
    },
    {
      "id": 4,
      "message": "4:Ответ на ответ 1"
    },
    {
      "id": 5,
      "message": "5:Ответ на ответ 2"
    }
  ],
  "profile": {
    "name": "typicode"
  }
}

Синхронное решение задачи:


#!/usr/bin/env python
import requests
from data_structures import IdNode, MsgNode, tree

api_url = "https://my-json-server.typicode.com/AcckiyGerman/demo/messages/"

def get_comment_by_id(x):
    url = api_url + str(x)
    r = requests.get(url).json()
    return r["message"]

def map_tree(node):
    return MsgNode(
        message=get_comment_by_id(node.id),
        children=[map_tree(child) for child in node.children]
    )

message_tree = map_tree(tree)
print(message_tree)

Асинхронное решение задачи:


#!/usr/bin/env python
import aiohttp
import asyncio
from data_structures import IdNode, MsgNode, tree

api_url = "https://my-json-server.typicode.com/AcckiyGerman/demo/messages/"
messages = {}
tasks = []

async def get_comment_by_id(x, session):
    global messages
    url = api_url + str(x)
    r = await session.get(url)
    data = await r.json()
    messages[x] = data['message']
    print(f"request {x} finished")

def initiate_tasks(node, session):
    """ starts a task for each message id in the tree, but not await for result """
    global tasks
    tasks.append(get_comment_by_id(node.id, session))
    for child in node.children:
        initiate_tasks(child, session)

def map_tree(node):
    global messages
    return MsgNode(
        message=messages[node.id],
        children=[map_tree(child) for child in node.children]
    )

async def main():
    async with aiohttp.ClientSession() as session:
        initiate_tasks(node=tree, session=session)
        await asyncio.gather(*tasks)
        message_tree = map_tree(tree)
        print(message_tree)

if __name__ == "__main__":
    asyncio.run(main())

Результаты:


$ time ./sync_fetch.py 
IdNode(id=1, children=[IdNode(id=2, children=[]), IdNode(id=3, children=[IdNode(id=4, children=[]), IdNode(id=5, children=[])])])
MsgNode(message='1:Оригинальный комментарий', children=[MsgNode(message='2:Ответ на комментарий 1', children=[]), MsgNode(message='3:Ответ на комментарий 2', children=[MsgNode(message='4:Ответ на ответ 1', children=[]), MsgNode(message='5:Ответ на ответ 2', children=[])])])

real    0m1,762s
user    0m0,181s
sys     0m0,012s

$ time ./async_fetch.py
IdNode(id=1, children=[IdNode(id=2, children=[]), IdNode(id=3, children=[IdNode(id=4, children=[]), IdNode(id=5, children=[])])])
request 2 finished
request 3 finished
request 1 finished
request 5 finished
request 4 finished
MsgNode(message='1:Оригинальный комментарий', children=[MsgNode(message='2:Ответ на комментарий 1', children=[]), MsgNode(message='3:Ответ на комментарий 2', children=[MsgNode(message='4:Ответ на ответ 1', children=[]), MsgNode(message='5:Ответ на ответ 2', children=[])])])

real    0m0,473s
user    0m0,137s
sys     0m0,020s

Выводы — асинхронный питон довольно сложен даже для такой простой задачи.


Я потратил около 3-х часов времени, чтобы написать правильное асинхронное решение, и мне не очень нравится результат. Рекурсивного обхода не получилось из-за необходимости собрать асинхронные обращения к удаленному серверу в одном месте, так что пришлось вводить еще две глобальные структуры messages и tasks


Синхронное решение заняло 10 минут. Правда я никогда в работе с асинхронным питоном не работал.


Может кто-либо подскажет, как можно короче написать.


код на гитхабе

Завязка и описание мира мне понравились.
Но не понравились частые отвлечённые диалоги не по теме, и конец истории уж очень скомканный.

Information

Rating
1,666-th
Registered
Activity