Как интерпретировать понимание вложенного списка словарей с помощью условных выражений в Python 3 - PullRequest
0 голосов
/ 08 мая 2019

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

Вопрос в следующем:

Школьный регистратор использует dict, где ключами являются курсы (т. Е. 'PHYS12', 'PHYS13'), а его значение равно set из 2-tuples. Каждый tuple имеет имя учащегося и буквенную оценку (возможно, за ним следует +/-. Пример регистратора выглядит так:

db = {'PHYS12': {('Bob', 'A'), ('David', 'C'), ('Carol', 'B')}, 'PHYS13': {('Bob',
'B'), ('Alice', 'A')}}

У нас также может быть следующее dict, которое связывает оценки с оценочными баллами:

SCHOOL = {'A+': 4.0, 'A': 4.0, 'A-': 3.7,
 'B+': 3.3, 'B': 3.0, 'B-': 2.7,
 'C+': 2.3, 'C': 2.0, 'C-': 1.7,
 'D+': 1.3, 'D': 1.0, 'D-': 0.7,
 'F' : 0.0}

Учитывая эту информацию, определите функцию: gpa_ex, которая принимает параметр int и возвращает dict, где ключи - это названия курсов, а значения - lists имен учащихся, отсортированные по убыванию оценка, оценка которой равна или превышает параметр gpa. Если gpa нескольких учеников одинаковы, они отображаются в алфавитном порядке по возрастанию. Например, если мы позвоним gpa_ex(2.7), он вернет:

{'PHYS12': ['Bob', 'Carol'], 'PHYS13': ['Alice', 'Bob']}

Итак, я понимаю, как это сделать, выполнив итерацию по db.items(), затем по кортежу и затем сравнив grade с переданным gpa. Моя проблема заключается в следующем ответе:

return {c: [s for _,s in sorted((-SCHOOL[g],s) for (s,g) in grades if SCHOOL[g] >= gpa)] for c,grades in db.items()}

Может ли кто-нибудь помочь мне разобрать этот ответ и объяснить мне, что происходит? Учитывая, что мы прошли только базовое понимание списка, т.е. [value for value in list], мне действительно очень трудно понять, какой синтаксис является законным, каковы правила для этого синтаксиса (то есть, когда я могу использовать переменную, когда я не могу), и просто как даже построить что-то вроде этот. Главным образом, какой мыслительный процесс стоит за его построением? Если у меня есть работающее нормальное решение для зацикливания, как бы я применил это для построения этого понимания?

Ответы [ 2 ]

2 голосов
/ 08 мая 2019

@ GreenCloakGuy уже дал отличное объяснение того, что делает код ответа.Я попытаюсь объяснить, как вы могли бы получить от фрагмента кода, использующего циклы for и промежуточные переменные, до такой конструкции понимания.

Это будет длинный

Не используя понимания, вам нужно написать что-то вроде следующего, чтобы получить желаемый результат вопроса:

def gpa_ex(gpa):
    result = {}

    for className, grades in db.items():
        scores = []

        for studentName, gradeLetter in grades:
            score = SCHOOL[gradeLetter]

            if score >= gpa:
                scores.append((-score, studentName))

        sortedScores = sorted(scores)

        result[className] = []

        for _, studentName in sortedScores:
            result[className].append(studentName)

    return result

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

В качестве первого шага мы можем использовать понимание списка, чтобы упростить построение result[className]:

def gpa_ex(gpa):
    result = {}

    for className, grades in db.items():
        scores = []

        for studentName, gradeLetter in grades:
            score = SCHOOL[gradeLetter]

            if score >= gpa:
                scores.append((-score, studentName))

        sortedScores = sorted(scores)

        # result[className] = []
        # 
        # for _, studentName in sortedScores:
        #     result[className].append(studentName)
        result[className] = [studentName for _, studentName in sortedScores]

    return result

sortedScores является просто результатомsorted(scores), нам для этого не нужна отдельная переменная, поскольку мы можем просто сделать это встроенным:

def gpa_ex(gpa):
    result = {}

    for className, grades in db.items():
        scores = []

        for studentName, gradeLetter in grades:
            score = SCHOOL[gradeLetter]

            if score >= gpa:
                scores.append((-score, studentName))

        # sortedScores = sorted(scores)
        result[className] = [studentName for _, studentName in sorted(scores)]

    return result

Прежде чем мы превратим создание scores в понимание, важно отметить, что мынеобходимо преобразовать букву «А» в оценку «4,0», чтобы ее можно было отсортировать.Или, на самом деле, "-4.0", чтобы сортировать по убыванию.

Прямо сейчас я использовал для этого отдельную переменную score.Давайте посмотрим, что произойдет, если мы просто отбросим эту переменную и дважды используем SCHOOL[gradeLetter]:

def gpa_ex(gpa):
    result = {}

    for className, grades in db.items():
        scores = []

        for studentName, gradeLetter in grades:
            # score = SCHOOL[gradeLetter]
            if SCHOOL[gradeLetter] >= gpa:
                scores.append((-SCHOOL[gradeLetter], studentName))

        result[className] = [studentName for _, studentName in sorted(scores)]

    return result

Похоже, это может быть условное понимание списка, не так ли?

def gpa_ex(gpa):
    result = {}

    for className, grades in db.items():
        # scores = []
        # 
        # for studentName, gradeLetter in grades:
        #     if SCHOOL[gradeLetter] >= gpa:
        #         scores.append((-SCHOOL[gradeLetter], studentName))
        scores = [(-SCHOOL[gradeLetter], studentName) for studentName, gradeLetter in grades if SCHOOL[gradeLetter] >= gpa]

        result[className] = [studentName for _, studentName in sorted(scores)]

    return result

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

def gpa_ex(gpa):
    result = {}

    for className, grades in db.items():
        # scores = [(-SCHOOL[gradeLetter], studentName) for studentName, gradeLetter in grades if SCHOOL[gradeLetter] >= gpa]
        result[className] = [studentName for _, studentName in sorted(
            [(-SCHOOL[gradeLetter], studentName) for studentName, gradeLetter in grades if SCHOOL[gradeLetter] >= gpa]
        )]

    return result

Хотя этот шаг не является обязательным, поскольку теперь он является аргументом функции для функции sorted(), мы могли бы опустить [] в понимании.Вместо понимания списка это создаст итеративный генератор .Для этого конкретного случая использования это не имеет никакого значения, поскольку sorted() может справиться с ними просто отлично.Лично я бы не стал беспокоиться, так как он сохраняет два символа, но создает немного больше накладных расходов, но это то, что ваш учитель дал в качестве ответа:

def gpa_ex(gpa):
    result = {}

    for className, grades in db.items():
        result[className] = [studentName for _, studentName in sorted(
            (-SCHOOL[gradeLetter], studentName) for studentName, gradeLetter in grades if SCHOOL[gradeLetter] >= gpa
        )]

    return result

Теперь, когда мы сократили это так далеко, мы можем видетьчто создание result dict может быть упрощено до понимания dict:

def gpa_ex(gpa):
    # result = {}
    # 
    # for className, grades in db.items():
    #     result[className] = [studentName for _, studentName in sorted(
    #         (-SCHOOL[gradeLetter], studentName) for studentName, gradeLetter in grades if SCHOOL[gradeLetter] >= gpa
    #     )]
    result = {
        className: [studentName for _, studentName in sorted(
            (-SCHOOL[gradeLetter], studentName) for studentName, gradeLetter in grades if SCHOOL[gradeLetter] >= gpa
        )] for className, grades in db.items()
    }

    return result

И теперь, когда мы снова создали переменную, которую используем только один раз, мы могли бы просто отбросить этопеременная в целом:

def gpa_ex(gpa):
    return {
        className: [studentName for _, studentName in sorted(
            (-SCHOOL[gradeLetter], studentName) for studentName, gradeLetter in grades if SCHOOL[gradeLetter] >= gpa
        )] for className, grades in db.items()
    }

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

def gpa_ex(gpa):
    return {
        c: [s for _, s in sorted(
            (-SCHOOL[g], s) for s, g in grades if SCHOOL[g] >= gpa
        )] for c, grades in db.items()
    }

Требуется немного практики, чтобы распознать ситуации, когда создание списка или диктата может быть сведено к пониманию.Возможно, не сразу было очевидно, что scores может быть условным списком, если удалить отдельную переменную score и просто дважды использовать SCHOOL[gradeLetter].Тем более, что это нелогично (нам, разработчикам, не нравится повторяться, поэтому, если мы делаем одно и то же несколько раз, мы склонны помещать их в переменную или функцию, чтобы мы могли использовать их повторно).Это то, что приходит с практикой и опытом.

Лично я думаю, что этот ответ заходит слишком далеко (если только весь вопрос не заключался в том, чтобы научить вас использовать понимание).Более короткий код не всегда лучший код, всегда помните о необходимости сопровождения.Просто представьте себе, как использовать этот код в реальном приложении, и через два года вам нужно немного подправить функцию gpa_ex из-за изменения бизнес-логики (возможно, вам нужно изменить порядок сортировки, или >= gpa должен стать> gpa).С какой версией этой функции вы бы предпочли столкнуться?Сокращенная конструкция понимания, полностью написанный код, с которого я начал, или одна из промежуточных версий?

2 голосов
/ 08 мая 2019

Кажется, у нас есть понимание списка, встроенное в понимание диктата. Другими словами, у нас есть список списков, который выглядит как

list_comprehension = [s for _,s in sorted((-SCHOOL[g],s) for (s,g) in grades if SCHOOL[g] >= gpa)]
{c:[list_comprehension] for c,grades in db.items()}

Это довольно просто. Теперь давайте посмотрим на [something]:

[s for _,s in sorted((-SCHOOL[g],s) for (s,g) in grades if SCHOOL[g] >= gpa)]

, который сам по себе является списочным пониманием кортежа:

tuple_comprehension = ((-SCHOOL[g], s) for (s,g) in grades if SCHOOL[g] >= gpa)
[s for s in sorted(tuple_comprehension)]

Итак, давайте посмотрим на tuple_comprehension:

((-SCHOOL[g], s) for (s,g) in grades if SCHOOL[g] >= gpa)

Условное выражение здесь означает, что (-SCHOOL[g], s) добавляется только к выходу , если SCHOOL[g] >= gpa оценивается как True. В противном случае этот элемент пропускается.


Таким образом, в целом это утверждение

return {c:                   # key:value, where the value is a list comprehension:
    [s for _,s in            # list comprehension, where the source-list is...
        sorted(              # ...the sorted version of the comprehension:
            (-SCHOOL[g],s) for (s,g) in grades if SCHOOL[g] >= gpa   comprehension
        )
     ]                       # end list comprehension
for c,grades in db.items()}  # end dict comprehension

Для таких методов, как sorted(), которые могут принимать произвольное количество переменных, вы можете передать понимание без квадратных или круглых скобок, которые вам обычно нужны. Выше я назвал это «пониманием кортежей», хотя это не совсем правильная терминология.

Что касается «когда вы определяете переменную в понимании списка»: это в предложении for, когда вы определяете, через что происходит перебор. Затем вы можете использовать эти переменные как в определении (пара объект / ключ-значение в начале, чтобы добавить), так и в условии (оператор if в конце) Например, в

... for (s,g) in grades ...

s и g определены здесь и могут использоваться где-либо еще в этом понимании (или любом другом понимании , вложенном в него ).

В целом в вашем примере vc и grades определены в понимании внешнего диктата и используются в определении , которое включает в себя понимания вложенного списка. Затем _ и s определяются в понимании внутреннего списка и используются в определении s (то есть _ игнорируется). И, наконец, g и другой s (никакого отношения к тому, что определено снаружи) определены в самом глубоком понимании и используются как в определении (-SCHOOL[g], s), так и в условии (if SCHOOL[g] >= gpa).

...