Передача цілого числа за посиланням у Python


96

Як я можу передати ціле число за посиланням у Python?

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

  1. Як я можу передати ціле число у функцію за допомогою посилання?
  2. Які найкращі практики?

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

Відповіді:


106

Це не зовсім так працює в 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

6
@ user102008 - Справедливий момент. На даний момент семантика стає дуже каламутною, я відчуваю. Зрештою, вони теж не "вказівники". Принаймні, точно не в тому сенсі, як у вас C. (Наприклад, їх не потрібно розмежовувати). У моїй ментальній моделі легше сприймати речі як "Імена" та "Предмети". Призначення прив'язує "Ім'я" зліва до "Об'єкта" справа. Коли ви викликаєте функцію, ви передаєте "Об'єкт" (ефективно прив'язуючи його до нового локального імені в межах функції).
mgilson

Це, звичайно, опис того, що таке посилання на python , але непросто знайти спосіб коротко описати його тим, хто не знайомий з термінологією.
mgilson

2
Не дивно, що нас плутають з термінологією. Тут ми описуємо це як виклик за об’єктом, а тут - як передачу за значенням . В іншому місці це називається "передача-посилання" із зірочкою щодо того, що це насправді означає ... В основному проблема полягає в тому, що спільнота не придумала, як це називати
mgilson,

1
Посилання на Python точно такі ж, як посилання на Java. А посилання на Java відповідають вказівникам JLS на об'єкти. І в основному існує повна бієкція між посиланнями Java / Python та вказівниками на C ++ на об'єкти в семантиці, і ніщо інше також не описує цю семантику. "Їх не потрібно переорієнтовувати" Ну, .оператор просто розмежовує ліву сторону. Оператори можуть бути різними на різних мовах.
user102008,

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] кожного його використання, як це показує відповідь Мґільсона.

Якщо вам потрібно зробити це для чогось більш значущого, напишіть атрибут, classякий має intатрибут, щоб ви могли просто встановити його. Звичайно, це змушує вас придумати хорошу назву для класу та для атрибута - якщо ви нічого не можете придумати, поверніться назад і прочитайте речення ще раз кілька разів, а потім використовуйте list.

Більш загально, якщо ви намагаєтеся перенести якусь ідіому Java безпосередньо на Python, ви робите це неправильно. Навіть коли є щось безпосередньо відповідне (як з static/ @staticmethod), ви все одно не хочете використовувати це в більшості програм Python лише тому, що ви б використовували це в Java.


JAVA не передає цілі числа за посиланням (цілі числа або будь-який об'єкт. Об'єкти в коробці також замінюються при призначенні).
Луїс Масуеллі,

@LuisMasuelli Ну, з примітивними коробками поводиться так само, як з об’єктами, єдине, що заважає їх використанню, як хоче ОП, це той факт, що коробки незмінні (а оскільки самі примітиви також незмінні, все це незмінюване, і їх можна змінити лише на змінний рівень)
Кролтан

Хорошим варіантом використання цього є підрахунок кількості дзвінків (загальної, а не глибини) всередині рекурсивної функції. Вам потрібен номер, який можна збільшити у всіх розгалужених дзвінках. Стандартний int просто не вирізає
z33k

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

Можливо, трохи більше самодокументування, ніж фокус list-of-length-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це насправді неправда. Цитую документи:arguments are passed using call by value (where the value is always an object reference, not the value of the object)
Астері

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.