Python . Подскажите, как упростить код генератора паролей

# Генератор паролей
# Пароль должен быть не меньше 8 символов
# должен содержать как минимум одну заглавную букву
# должен содержать как минимум одну строчную букву
# должен содержать как минимум одну цифру
# должен содержать как минимум один спец-символ (!&? и прочие)
# а еще он не должен содержать некоторые символы, чтобы не поломать bash-скрипты

import random
import string

password = []
forbidden = ['+', '-', ',', '`', '^']  # запрещенные символы

lis_uppercase = []                     # создание списков из строк ascii
for f in string.ascii_uppercase:
    lis_uppercase.append(f)

lis_lowercase = []
for f in string.ascii_lowercase:
    lis_lowercase.append(f)

lis_octdigits = []
for f in string.octdigits:
    lis_octdigits.append(f)

lis_punctuation = []
for f in string.punctuation:
    lis_punctuation.append(f)
for f in forbidden:                    # удаление запрещенных символов из списка
    lis_punctuation.remove(f)

password.append(random.choice(lis_uppercase))  # по одному элементу с каждого списка,
password.append(random.choice(lis_lowercase))  # всего списков 4
password.append(random.choice(lis_octdigits))
password.append(random.choice(lis_punctuation))

length = int(input('Length: '))  # ввод длины пароля
while length <= 0 or length > 15:
    print('Ошибка ввода')
    length = int(input('Length: '))

lis_all = lis_uppercase + lis_lowercase + lis_octdigits + lis_punctuation  # обьединяем списки

for f in range(length - 4):  # от длины вычитаем количество списков(т.е. 4 элемента)
    length -= 1
    password.append(random.choice(lis_all))  # добавляем остальные символы
    if length == 0:
        break

password = ''.join(password)
print(password)

Ответы (2 шт):

Автор решения: Zhihar

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

import random

password = ''

while True:
    dictionary = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!&?'

    password = ''.join(random.choices(dictionary, k=random.randint(8, 16)))

    if any(l.isdigit() for l in password) and any(l.isupper() for l in password) and any(l.islower() for l in password) and any(l in '!&?' for l in password):
        break

print(password)

А вот так выглядит однопроходный алгоритм:

import random

dictionary = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!&?'
dictionaries = ['abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789', '!&?']

password = [random.choice(d) for d in dictionaries] + random.choices(dictionary, k=random.randint(8, 16) - 4)
random.shuffle(password)
password = ''.join(password)

print(password)

ну или вот так, чтобы избавиться от дублирования словарей:

import random

dictionaries = ['abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '0123456789', '!&?']

password = [random.choice(d) for d in dictionaries] + random.choices(''.join(dictionaries), k=random.randint(8, 16) - 4)
random.shuffle(password)
password = ''.join(password)

print(password)
→ Ссылка
Автор решения: GrAnd
import random, string

forbidden = set('+-,`^')  # запрещенные символы
special_symbols = set(string.punctuation) - forbidden

password_len = 10
password = []
all_symbols = set()
for seq in (string.ascii_lowercase, string.ascii_uppercase, string.digits, special_symbols):
    password.append(random.choice(list(seq)))
    all_symbols.update(seq)

password.extend(random.choices(list(all_symbols), k=password_len-4))
random.shuffle(password)
password = ''.join(password)
print(password)
→ Ссылка