Python: добавление атрибутов в изменяемые таблицы внутри функции - PullRequest
0 голосов
/ 01 марта 2020

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

d = {'a': 0}

def my_fun(x):
    for i, el in enumerate(x):
        d[el] = i + 1

Вызов my_fun(['b', 'c']), а затем print(d) выведет {'a':0, 'b':1, 'c':2}, который Это хорошо. Однако то же самое можно сделать, добавив оператор возврата в функцию (хотя и не обязательно). Или даже оператор return плюс второй аргумент в функции, представляющей dict:

d = {'a': 0}

def my_fun(x, d):
    for i, el in enumerate(x):
        d[el] = i + 1
    return d

И оператор return, и второй аргумент здесь избыточны, я просто нахожу, что они помогают с точки зрения ясности. Это легче читать. Вызов d = my_fun(['b', 'c'], d) и затем print(d) напечатает снова {'a':0, 'b':1, 'c':2}

Наконец, вы можете передать копию изменяемого объекта: d = my_fun(['b', 'c'], d.copy()), который, вероятно, больше pythoni c, но я не знаю например, если копирование является хорошей практикой с точки зрения управления памятью.

Что считается здесь лучшей практикой? Как вы обрабатываете эти типы и добавляете атрибуты в изменяемые таблицы внутри функций?

Ответы [ 2 ]

2 голосов
/ 01 марта 2020

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

В этом простом примере, как вы говорите, вы можете скопировать диктовку. ИМО ты должен сделать это в функции. С самого начала вам нужно четко понимать, какова структура дикта, потому что вам может потребоваться глубокая копия.

def my_fun(x, d):
    d = d.copy()
    for i, el in enumerate(x):
        d[el] = i + 1
    return d

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

def my_func(x, d):
    result = {el: i for i, el in enumerate(x)}
    result.update(d)
    return result

Для более сложной вещи у вас может быть класс для инкапсуляции всей вещи.

class Foo:
    def __init__(self, d):
        self.d = d

    def update(self, x):
        for i, el in enumerate(x):
            self.d[el] = i
1 голос
/ 01 марта 2020

Ваши два примера не эквивалентны. Они выглядят так только потому, что вы назвали параметр функции таким же, как глобальный. Эквивалентная функция:

d = {'a': 0}

def my_fun(x, foo):
    for i, el in enumerate(foo):
        foo[el] = i + 1
    return foo

Но эта функция не "pythoni c". Это нормально, чтобы изменить передаваемый объект, но когда вы делаете это, не возвращайте его. Если вы сделаете копию объекта в функции, верните ее. Все, что вы сделали, это создали двусмысленность - я бы с уверенностью предположил, что исходный текст не был изменен. Что касается ясности, для этого нужны c строки. Теперь каждый, кто печатает help(my_fun), знает, что происходит.

def my_fun(x, foo):
    """Update foo with an enumeration of x"""
    for i, el in enumerate(foo):
        foo[el] = i + 1

Ваш первый пример самый рискованный. Это делает тихое обновление глобального объекта. Мы стараемся избегать подобных вещей. И когда все готово, строка do c и, как мы надеемся, имя самой функции проясняют, что происходит какая-то шалость-панк. Обновление dict, переданного в вашу функцию, немного более обобщенно c, чем копирование его, потому что вызывающая сторона может решить, следует ли копировать обновляемые данные или нет. Конечно, поскольку вы больше не возвращаете обновленный объект, для вызывающей стороны требуется немного больше работы.

d_copy = d.copy()
my_fun(['b', 'c'], d_copy)

Это не так уж плохо. Код понятен. Не то чтобы у нас заканчивались переводы строк или что-то в этом роде.

...