Передача целого числа по ссылке в Python


97

Как передать целое число по ссылке в Python?

Я хочу изменить значение переменной, которую передаю функции. Я читал, что в Python все передается по значению, но должен быть простой трюк. Например, в Java можно передать эталонные типы Integer, Longи т.д.

  1. Как передать целое число в функцию по ссылке?
  2. Какие лучшие практики?

см. здесь хороший, хотя и слегка запутанный способ обернуть ваши ints в анонимный класс (который является изменяемым), который будет вести себя как «ссылка»: stackoverflow.com/a/1123054/409638 т.е. ref = type ('', () , {'n': 1})
Роберт

Ответы:


108

В Python это не совсем так. Python передает ссылки на объекты. Внутри вашей функции у вас есть объект - вы можете изменить этот объект (если возможно). Однако целые числа неизменяемы . Один из способов обхода - передать целое число в контейнер, который можно изменить:

def change(x):
    x[0] = 3

x = [1]
change(x)
print x

Это в лучшем случае уродливо / неуклюже, но вы не добьетесь большего успеха в Python. Причина в том, что в Python assignment ( =) берет любой объект, являющийся результатом правой стороны, и привязывает его к тому, что находится слева * (или передает его соответствующей функции).

Понимая это, мы можем понять, почему нет способа изменить значение неизменяемого объекта внутри функции - вы не можете изменить ни один из его атрибутов, потому что он неизменяемый, и вы не можете просто присвоить «переменной» новый значение, потому что тогда вы фактически создаете новый объект (который отличается от старого) и даете ему имя, которое старый объект имел в локальном пространстве имен.

Обычно обходной путь - просто вернуть нужный объект:

def multiply_by_2(x):
    return 2*x

x = 1
x = multiply_by_2(x)

* В первом примере выше 3фактически передается в x.__setitem__.


11
«Python передает объекты». Нет. Python передает ссылки (указатели на объекты).
user102008 08

6
@ user102008 - Справедливый момент. Я чувствую, что семантика в этот момент становится очень мутной. В конечном счете, они тоже не «указатели». По крайней мере, точно не в том смысле, в котором вы говорите C. (Например, их не нужно разыменовывать). В моей ментальной модели легче думать о вещах как о «именах» и «объектах». Присвоение связывает «Имя» (я) слева с «Объектом» (ами) справа. Когда вы вызываете функцию, вы передаете «Объект» (фактически привязывая его к новому локальному имени внутри функции).
mgilson

Это, конечно, описание того, что такое ссылки на Python , но нелегко найти способ кратко описать его для тех, кто не знаком с терминологией.
mgilson

2
Неудивительно, что мы путаемся с терминологией. Здесь он описан как вызов по объекту, а здесь - как передача по значению . В другом месте это называется «передача по ссылке» со звездочкой, означающей, что это на самом деле означает ... По сути, проблема в том, что сообщество не выяснило, как это назвать
mgilson

1
Ссылки Python точно такие же, как ссылки Java. И ссылки Java соответствуют указателям JLS на объекты. И, по сути, существует полное взаимное соответствие между ссылками Java / Python и указателями C ++ на объекты в семантике, и ничто другое также не описывает эту семантику. «Их не нужно разыменовывать» Ну, .оператор просто разыменовывает левую часть. Операторы на разных языках могут быть разными.
user102008 08

31

В большинстве случаев, когда вам нужно передать по ссылке, вам нужно вернуть более одного значения обратно вызывающей стороне. «Лучшая практика» - использовать несколько возвращаемых значений, что намного проще сделать в Python, чем в таких языках, как Java.

Вот простой пример:

def RectToPolar(x, y):
    r = (x ** 2 + y ** 2) ** 0.5
    theta = math.atan2(y, x)
    return r, theta # return 2 things at once

r, theta = RectToPolar(3, 4) # assign 2 things at once

9

Не совсем прямая передача значения, а использование его, как если бы оно было передано.

x = 7
def my_method():
    nonlocal x
    x += 1
my_method()
print(x) # 8

Предостережения:

  • nonlocal был введен в Python 3
  • Если охватывающая область является глобальной, используйте globalвместо nonlocal.

7

На самом деле лучше всего сделать шаг назад и спросить, действительно ли вам это нужно. Почему вы хотите изменить значение переменной, которую передаете функции?

Если вам нужно сделать это для быстрого взлома, самый быстрый способ - передать listцелое число и прикрепить его к [0]каждому его использованию, как демонстрирует ответ mgilson.

Если вам нужно сделать это для чего-то более значительного, напишите, classчто имеет intатрибут в качестве атрибута, чтобы вы могли просто установить его. Конечно, это заставляет вас придумать хорошее имя для класса и для атрибута - если вы ничего не можете придумать, вернитесь и прочтите предложение еще раз несколько раз, а затем используйте list.

В более общем смысле, если вы пытаетесь перенести какую-то идиому Java непосредственно на Python, вы делаете это неправильно. Даже когда есть что-то прямо соответствующее (как с static/ @staticmethod), вы все равно не хотите использовать это в большинстве программ Python только потому, что вы использовали бы это в Java.


JAVA не передает целые числа по ссылке (целые числа или любые объекты. Объекты в коробках также заменяются при назначении).
Луис Масуэлли

@LuisMasuelli Ну, примитивы в штучной упаковке обрабатываются так же, как объекты, единственное, что мешает их использованию, как того хочет OP, - это тот факт, что блоки неизменяемы (а поскольку сами примитивы также неизменны, все это неизменяемо и может быть изменено только в переменный уровень)
Кролтан

Хороший вариант использования для этого - подсчет количества вызовов (всего, а не глубины) внутри рекурсивной функции. Вам нужен номер, который можно увеличивать для всех исходящих вызовов. Стандартный int просто не
подходит

4

В Python каждое значение является ссылкой (указателем на объект), как и в Java без примитивов. Кроме того, как и в Java, Python имеет передачу только по значению. Итак, семантически они почти одинаковы.

Поскольку вы упоминаете Java в своем вопросе, я хотел бы увидеть, как вы добиваетесь того, чего хотите от Java. Если вы можете показать это на Java, я могу показать вам, как это сделать в точности эквивалентно на Python.


4

NumPy массив с одним элементом является изменяемым и тем не менее в большинстве случаев, это может быть оценена , как если бы это была числовая переменная Python. Следовательно, это более удобный контейнер по ссылочным номерам, чем одноэлементный список.

    import numpy as np
    def triple_var_by_ref(x):
        x[0]=x[0]*3
    a=np.array([2])
    triple_var_by_ref(a)
    print(a+1)

выход:

3

3
class PassByReference:
    def Change(self, var):
        self.a = var
        print(self.a)
s=PassByReference()
s.Change(5)     

3

Может это не питонический способ, но вы можете это сделать

import ctypes

def incr(a):
    a += 1

x = ctypes.c_int(1) # create c-var
incr(ctypes.ctypes.byref(x)) # passing by ref

Умный человек.
xilpex

2

Может быть, немного более самодокументированный, чем уловка со списком длины 1, является старый трюк с пустым типом:

def inc_i(v):
    v.i += 1

x = type('', (), {})()
x.i = 7
inc_i(x)
print(x.i)

Или оберните число в класс: github.com/markrages/python_mutable_number
markrages

0

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


3
Думаю, потому что everything is passed by valueэто не совсем так. Цитата docs:arguments are passed using call by value (where the value is always an object reference, not the value of the object)
Astery

1
Списки, объекты и словари передаются по ссылке
AN88

2
Если бы это было так, присвоение параметру функции было бы отражено на сайте вызова. Astery цитирует передачу по значению, и эти значения являются ссылками на объекты.
рекурсивный

0

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

class Thing:
  def __init__(self,a):
    self.a = a
def dosomething(ref)
  ref.a += 1

t = Thing(3)
dosomething(t)
print("T is now",t.a)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.