skip to Main Content

I have a the following code to get some messages from a telegram bot with python:

useful_messages = [i["message"] for i in response["result"] if i["message"]["from"]["id"] == int(chat_id) and i["message"]["date"] > last_time]

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
KeyError: 'message'

chat_id is the id of the user I’m interested in, and last_time is used to avoid reading the whole chat with the user.

It worked for some months until today I hit an "edge case":

[i.keys() for i in response["result"]]

[dict_keys(['update_id', 'message']), 
 dict_keys(['update_id', 'message']), 
 dict_keys(['update_id', 'edited_message']), 
 dict_keys(['update_id', 'message'])]

As you can see, one of the messages was edited, and its key is no longer message but edited_message, causing the KeyError above.

I know I can use a for loop, check for either key (message or edited_message) and continue with the message validation (date and id) and extraction. But I wondered if it is possible to check for either key in the dictionary, thus keeping the list/dictionary comprehension (a one-liner solution would be ideal).

I also thought of replacing the edited_message key, if present, by following any of the procedures shown in the answers to this question. Sadly they are hardly one-liners, so the for loop seems to be a less verbose and convoluted solution.

Of course, I’m open to any other solution (or programming logic) that will result in better code.

I’m still new to python, so I’d appreciate your detailed explanations, if complex solutions are offered.

2

Answers


  1. With python>=3.8 you can use the walrus operator

    useful_messages = [x for i in response["result"]
                       if (x := i.get("message", i.get("edited_message")))["from"]["id"] == int(chat_id)
                       and x["date"] > last_time]
    

    Without it would be less performant

    You could do i.get("message", i.get("edited_message")) but keeping the list comprehension would mean repeat it 3 times instead of one, which isn’t very performant (unless you don’t have many items (not millions))

    useful_messages = [i.get("message", i.get("edited_message")) for i in response["result"] 
                       if i.get("message", i.get("edited_message"))["from"]["id"] == int(chat_id) 
                       and i.get("message", i.get("edited_message"))["date"] > last_time]
    

    A for loop would be cleaner then, a cleaner and performant code values more than a short code

    useful_messages = []
    
    for i in response["result"]:
        msg = i.get("message")
        if msg is None:
            msg = i["edited_message"]
    
        if msg["from"]["id"] == int(chat_id) and msg["date"] > last_time:
            useful_messages.append(msg)
    
    Login or Signup to reply.
  2. for nested comprehension I like to separate lines, but you can one-line it if you don’t care.

    messages = [
        {'message': {'from': {'id': 1}, 'date': 'new'}},
        {'message': {'from': {'id': 1}, 'date': 'val'}},
        {'edited_message': {'from': {'id': 1}, 'date': 'new'}},
        {'edited_message': {'from': {'id': 1}, 'date': 'val'}},
        {'garbage': {'from': {'id': 1}, 'date': 'val'}},
        ]
    
    useful_messages = [
        message[key]
        for message in messages
        for key in message
        if key in ('message', 'edited_message')
        and message[key]['from']['id'] == 1
        and message[key]['date'] == 'val'
        ]
    

    output:

    [{'from': {'id': 1}, 'date': 'val'}, {'from': {'id': 1}, 'date': 'val'}]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search