Muito bom! Complementando, todas estas opções de formatação podem ser encontradas na documentação (é bem ampla, com várias outras opções, sugiro a leitura).
Um detalhe interessante é que os parâmetros de formatação (tamanho, alinhamento, etc) podem ser dinâmicos:
def fstring(texto, fill_char, align, size):
print(f'{texto:{fill_char}{align}{size}}')
fstring('olá', '+', '<', 10) # olá+++++++
fstring('olá', '.', '^', 15) # ......olá......
E não se resume a variáveis, eu posso colocar qualquer expressão, desde que resulte em um caractere válido:
# escolhe as opções aleatoriamente
from random import choice
aligns = ['<', '>', '^']
fill_chars = ['-', '.', '+', '_', '*']
sizes = [10, 15, 20]
# cada vez que rodar, vai escolher opções diferentes
print(f'{"olá":{choice(fill_chars)}{choice(aligns)}{choice(sizes)}}')
Repare também que a string a ser formatada não precisa estar em uma variável, eu posso colocá-la diretamente ali. A única exigência é que se use aspas diferentes da f-string.
Vale lembrar também que é possível ter f-strings aninhadas:
print(f'{", ".join(f"{n:03}" for n in range(10)):_>60}')
# ____________000, 001, 002, 003, 004, 005, 006, 007, 008, 009
O importante é que a f-string interna use aspas diferentes da externa.