Понимание стиля вызова Python по методу передачи аргументов функции

Я не уверен, что понимаю концепцию вызова Python по объектным стилям передачи аргументов функции (здесь объясняется http://effbot.org/zone/call-by-object.htm). Кажется, не хватает примеров, чтобы прояснить эту концепцию хорошо (или мой google-fu, вероятно, слабый!: D)

Я написал эту маленькую надуманную программу Python, чтобы попытаться понять эту концепцию.

def foo( itnumber, ittuple,  itlist, itdict   ):
    itnumber +=1 
    print id(itnumber) , itnumber 

    print id(ittuple)  , ittuple

    itlist.append(3.4)
    print id(itlist)   , itlist

    itdict['mary']  = 2.3
    print id(itdict),    itdict



# Initialize a number, a tuple, a list and a dictionary
tnumber = 1
print id( tnumber ), tnumber 

ttuple  = (1, 2, 3)
print id( ttuple ) , ttuple

tlist   = [1, 2, 3]
print id( tlist ) , tlist

tdict = tel = {'jack': 4098, 'sape': 4139}
print '-------'
# Invoke a function and test it
foo(tnumber, ttuple, tlist , tdict)

print '-------'
#Test behaviour after the function call is over
print id(tnumber) , tnumber 
print id(ttuple)  , ttuple
print id(tlist)   , tlist
print id(tdict),  tdict

Выход программы

146739376 1
3075201660 (1, 2, 3)
3075103916 [1, 2, 3]
3075193004 {'sape': 4139, 'jack': 4098}

---------

146739364 2
3075201660 (1, 2, 3)
3075103916 [1, 2, 3, 3.4]
3075193004 {'sape': 4139, 'jack': 4098, 'mary': 2.3}

---------

146739376 1
3075201660 (1, 2, 3)
3075103916 [1, 2, 3, 3.4]
3075193004 {'sape': 4139, 'jack': 4098, 'mary': 2.3}

Как вы можете видеть, кроме переданного целого, идентификатор объекта (который, как я понимаю, относится к местоположению memeory) остается неизменным.

Итак, в случае целого числа он был (эффективно) передан по значению, а другая структура данных была (фактически) передана по ссылке. Я попытался изменить список, номер и словарь, чтобы просто проверить, были ли изменены структуры данных. Число не было в списке, а словарь были.

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

Для более сложных структур данных (например, массивов numpy и т.д.) есть ли быстрое правило для распознать, какие аргументы будут передаваться по ссылке, а какие - по значению?

Ответ 1

Ключевое различие заключается в том, что в языке C-стиля переменная представляет собой поле в памяти, в которое вы помещаете вещи. В Python переменная - это имя.

Python не является ни вызовом по ссылке, ни вызовом. Это нечто более разумное! (На самом деле, я изучил Python, прежде чем я узнал более распространенные языки, поэтому вызов по значению и вызов по ссылке кажутся мне очень странными.)

В Python есть вещи и есть имена. Списки, целые числа, строки и пользовательские объекты - это все. x, y и z - имена. Запись

x = []

означает "построить новую вещь [] и присвоить ей имя x". Запись

x = []
foo = lambda x: x.append(None)
foo(x)

означает "построить новую вещь [] с именем x, построить новую функцию (это еще одна вещь) с именем foo и вызвать foo на предмет с именем x". Теперь foo просто добавляет None к тому, что он получил, поэтому это сводится к "добавить None в пустой список". Запись

x = 0
def foo(x):
    x += 1
foo(x)

означает "построить новую вещь 0 с именем x, построить новую функцию foo и вызвать foo на x". Внутри foo присваивание просто говорит "переименовать x в 1 плюс то, что раньше было", но это не меняет вещь 0.

Ответ 2

Другие уже опубликовали хорошие ответы. Еще одна вещь, которая, я думаю, поможет:

 x = expr

оценивает expr и связывает x с результатом. С другой стороны:

 x.operate()

делает что-то в x и, следовательно, может его изменить (в результате чего один и тот же базовый объект имеет другое значение).

В смешные случаи входят такие вещи, как:

 x += expr

которые переводятся на x = x + expr (перевязка) или x.__iadd__(expr) (изменение), иногда очень своеобразно:

>>> x = 1
>>> x += 2
>>> x
3

(поэтому x был отскок, так как целые числа неизменяемы)

>>> x = ([1], 2)
>>> x
([1], 2)
>>> x[0] += [3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> x
([1, 3], 2)

Здесь x[0], который сам изменен, был мутирован на месте; но затем Python также попытался mutate x сам (как с x.__iadd__), который был ошибочен, поскольку кортежи неизменяемы. Но к тому времени x[0] уже был мутирован!

Ответ 3

Числа, строки и кортежи в Python неизменяемы; с использованием расширенного назначения будет восстановить имя.

Ваши другие типы просто мутированы и остаются тем же объектом.

Ответ 4

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

x=[0]
def foo(x)
   x[0] += 1
foo(x)

foo увеличит значение x [0]

Снова я бы хотел, чтобы кто-то сказал мне правильный способ сделать это.