함수가 호출자가 인식 한 일부 인수를 수정할 수 있지만 다른 인수는 수정할 수없는 이유는 무엇입니까?


182

변수 범위에 대한 Python의 접근 방식을 이해하려고합니다. 이 예에서, 안에 인식 된 것처럼 f()의 값을 변경할 수 있지만 ? 의 값은 변경할 수없는 이유는 무엇입니까?xmain()n

def f(n, x):
    n = 2
    x.append(4)
    print('In f():', n, x)

def main():
    n = 1
    x = [0,1,2,3]
    print('Before:', n, x)
    f(n, x)
    print('After: ', n, x)

main()

산출:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]

7
여기에 잘 설명되어 있습니다. nedbatchelder.com/text/names.html
Roushan

답변:


212

일부 답변에는 함수 호출 컨텍스트에서 "복사"라는 단어가 포함되어 있습니다. 혼란 스럽습니다.

파이썬은 복사하지 않습니다 객체 함수 호출을 진행하는 동안 통과를 이제까지 .

기능 매개 변수는 이름 입니다. 함수를 호출하면 파이썬은이 파라미터를 호출 한 객체에 (호출자 범위의 이름을 통해) 바인딩합니다.

객체는 (리스트와 같은) 변경 가능하거나 (파이썬의 정수, 문자열과 같이) 변경할 수 없습니다. 변경할 수있는 가변 객체. 이름을 변경할 수 없으며 다른 객체에 바인딩 할 수 있습니다.

귀하의 예에 대해없는 범위 또는 네임 스페이스 가에 관한 것입니다, 이름 및 바인딩객체의 가변성 파이썬한다.

def f(n, x): # these `n`, `x` have nothing to do with `n` and `x` from main()
    n = 2    # put `n` label on `2` balloon
    x.append(4) # call `append` method of whatever object `x` is referring to.
    print('In f():', n, x)
    x = []   # put `x` label on `[]` ballon
    # x = [] has no effect on the original list that is passed into the function

다른 언어의 변수와 파이썬의 이름의 차이점대한 멋진 그림이 있습니다 .


3
이 기사는 문제를 더 잘 이해하는 데 도움이되었으며 해결 방법과 몇 가지 고급 사용법을 제안합니다. Python의 기본 매개 변수 값
Gfy

@Gfy, 나는 전에 비슷한 예를 보았지만 실제 상황을 묘사하지는 않습니다. 전달 된 것을 수정하는 경우 기본값을 지정하는 것은 의미가 없습니다.
Mark Ransom

@MarkRansom, 다음과 같이 선택적 출력 대상을 제공하려는 경우 의미가 있다고 생각합니다 def foo(x, l=None): l=l or []; l.append(x**2); return l[-1].
야누 LENAR

Sebastian 코드의 마지막 줄에 대해 "# 위의 내용은 원본 목록에 영향을 미치지 않습니다"라고 말했습니다. 그러나 제 생각에는 "n"에만 영향을 미치지 않지만 main () 함수에서 "x"가 변경되었습니다. 나 맞아?
user17670

1
@ user17670 : x = []in f()x기본 기능 의 목록 에 영향을 미치지 않습니다 . 의견을 좀 더 구체적으로 업데이트했습니다.
jfs

15

당신은 이미 많은 답변을 얻었으며 JF Sebastian에 널리 동의하지만 이것이 바로 가기로 유용 할 수 있습니다.

을 볼 때마다 함수 범위 내에서 이름 바인딩을 varname =만듭니다 . 이전에 바인드 된 값 은 이 범위 내에서 손실 됩니다 .varname

varname.foo()언제든에 메소드를 호출하는 것을 볼 수 있습니다 varname. 이 방법은 varname (예 :)을 변경할 수 있습니다 list.append. varname(또는 오히려 varname이름이 지정된 개체 )가 둘 이상의 범위에 존재할 수 있으며 같은 개체이므로 모든 범위에서 모든 변경 사항을 볼 수 있습니다.

[ global키워드는 첫 번째 경우에 예외를 만듭니다.]


13

f실제로 값을 변경하지는 않습니다 x(항상 목록의 인스턴스에 대한 참조와 동일 함). 오히려이 목록 의 내용 을 변경합니다 .

두 경우 모두 참조 사본 이 함수에 전달됩니다. 함수 안에서

  • n새로운 가치가 부여됩니다. 함수 내부의 참조 만이 아니라 함수 내부의 참조 만 수정됩니다.
  • x새로운 값이 할당되지 않습니다 : 함수 내부와 외부의 참조는 수정되지 않습니다. 대신 x 이 수정되었습니다.

모두 이후 x함수 안쪽과 바깥 쪽은 동일한 값을 참조 두 변형 참조. 반대로 n함수 내부와 외부 는 함수 내부에서 재 할당 된 후 다른 값을 n나타냅니다.


8
"복사"는 잘못된 것입니다. 파이썬에는 C와 같은 변수가 없습니다. 파이썬의 모든 이름은 참조입니다. 이름을 수정할 수 없으며 다른 객체에 바인딩 할 수 있습니다. 파이썬에서 변경 가능하고 변경 불가능한 객체 에 대해 이야기하는 것이 의미 가 있습니다.
jfs

1
@JF Sebastian : 당신의 진술은 기만적입니다. 숫자를 참조로 생각하는 것은 유용하지 않습니다.
Pitarou

9
@dysfunctor : 숫자는 불변 개체에 대한 참조입니다. 오히려 다른 방법으로 생각하고 싶다면 이상한 이상한 특별한 사례가 있습니다. 그것들을 불변이라고 생각하면 특별한 경우는 없습니다.
S.Lott

@ S.Lott : Guido van Rossum은 어떤 일이 벌어지고 있는지에 관계없이 프로그래머가 숫자를 숫자로만 할 수 있도록 Python을 디자인하는 데 많은 노력을 기울였습니다.
Pitarou

1
@JF, 참조가 복사됩니다.
habnabit

7

혼란을 줄이기 위해 변수 이름을 바꿀 것입니다. n- > nf 또는 nmain . x- > xf 또는 xmain :

def f(nf, xf):
    nf = 2
    xf.append(4)
    print 'In f():', nf, xf

def main():
    nmain = 1
    xmain = [0,1,2,3]
    print 'Before:', nmain, xmain
    f(nmain, xmain)
    print 'After: ', nmain, xmain

main()

당신이 함수를 호출 할 때 F를 , 파이썬 런타임의 사본을 만들어 xmain 과 양수인을 XF , 유사의 사본 할당 nmain을NF .

n 의 경우 복사되는 값은 1입니다.

x 의 경우 복사되는 값이 리터럴 목록 [0, 1, 2, 3]아닙니다 . 해당 목록에 대한 참조 입니다. xfxmain 은 동일한 목록을 가리 키므로 xf 를 수정 하면 xmain 도 수정 됩니다 .

그러나 다음과 같은 것을 작성해야한다면 :

    xf = ["foo", "bar"]
    xf.append(4)

xmain 이 변경되지 않았 을 알 수 있습니다. xf = [ "foo", "bar"] 줄에서 xf 목록 을 가리 키도록 변경 했기 때문 입니다. 이 새 목록을 변경해도 xmain이 계속 가리키는 목록에는 영향을 미치지 않습니다 .

희망이 도움이됩니다. :-)


2
"n의 경우, 복사되는 값 ..."-이것은 잘못된 것입니다 (참조를 세지 않는 한) 여기서 복사가 수행되지 않습니다. 대신, 파이썬은 실제 객체를 가리키는 '이름'을 사용합니다. nf 및 xf nf = 2는 이름 nf이로 변경 될 때까지 nmain 및 xmain 을 가리 킵니다 2. 숫자는 변경할 수 없으며 목록은 변경할 수 있습니다.
Casey Kuball

2

목록이 변경 가능한 객체이기 때문입니다. x를 [0,1,2,3]의 값으로 설정하지 않고 [0,1,2,3] 객체에 레이블을 정의합니다.

f () 함수를 다음과 같이 선언해야합니다.

def f(n, x=None):
    if x is None:
        x = []
    ...

3
가변성과는 아무런 관련이 없습니다. 당신이 할 경우 x = x + [4]대신 x.append(4)목록을 변경할 수 있지만, 당신은뿐만 아니라 발신자에 변화를 볼 것입니다. 그것은과 관련이 있는 경우 가 참으로 변이된다.
glglgl

1
OTOH, 당신이 할 경우 x += [4]다음 x단지에 무슨 일처럼, 돌연변이 x.append(4)호출자가 변경 사항을 볼 수 있도록.
PM 2Ring

2

n은 int (불변)이고 사본은 함수로 전달되므로 함수에서 사본을 변경합니다.

X는 목록 (변경 가능)이며 포인터 의 사본 이 전달되므로 x.append (4)는 목록의 내용을 변경합니다. 그러나 함수에서 x = [0,1,2,3,4]라고 말하면 main ()에서 x의 내용을 변경하지 않을 것입니다.


3
"포인터 복사"문구를보십시오. 두 장소 모두 객체에 대한 참조를 얻습니다. n은 불변 객체에 대한 참조이다. x는 가변 객체에 대한 참조입니다.
S.Lott

2

함수가 완전히 다른 변수로 다시 작성되고 우리 는 그 위에 id 를 호출 하면 그 점을 잘 보여줍니다. 나는 이것을 처음에는 얻지 못했고 훌륭한 설명 과 함께 jfs의 게시물을 읽었 으므로 나 자신을 이해 / 확신하려고했습니다.

def f(y, z):
    y = 2
    z.append(4)
    print ('In f():             ', id(y), id(z))

def main():
    n = 1
    x = [0,1,2,3]
    print ('Before in main:', n, x,id(n),id(x))
    f(n, x)
    print ('After in main:', n, x,id(n),id(x))

main()
Before in main: 1 [0, 1, 2, 3]   94635800628352 139808499830024
In f():                          94635800628384 139808499830024
After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024

z와 x의 ID는 동일합니다. 기사와 동일한 기본 구조에 대해 다른 태그 만 있습니다.


0

파이썬은 올바른 방식으로 생각하면 순수한 가치 전달 언어입니다. 파이썬 변수는 객체의 위치를 ​​메모리에 저장합니다. 파이썬 변수는 객체 자체를 저장하지 않습니다. 변수를 함수에 전달하면 변수 가 가리키는 객체의 주소 사본 이 전달 됩니다.

이 두 기능을 대조

def foo(x):
    x[0] = 5

def goo(x):
    x = []

이제 쉘에 입력하면

>>> cow = [3,4,5]
>>> foo(cow)
>>> cow
[5,4,5]

이것을 goo와 비교하십시오.

>>> cow = [3,4,5]
>>> goo(cow)
>>> goo
[3,4,5]

첫 번째 경우, 우리는 cow의 주소를 foo로 복사하고 foo는 거기에있는 객체의 상태를 수정했습니다. 개체가 수정됩니다.

두 번째 경우 암소 주소 사본을 goo에 전달합니다. 그런 다음 goo가 해당 사본을 변경합니다. 효과 : 없음

나는 이것을 핑크 하우스 원칙 이라고 부릅니다 . 주소 사본을 만들어 화가에게 해당 주소의 집을 분홍색으로 칠하라고 지시하면 분홍색 집이 생깁니다. 화가에게 주소 사본을 제공하고 새 주소로 변경하라고하면 집 주소는 바뀌지 않습니다.

설명은 많은 혼란을 제거합니다. 파이썬은 값으로 주소 변수 저장소를 전달합니다.


포인터 값에 의한 순수 패스는 올바른 방법으로 생각하면 참조에 의한 패스와 크게 다르지 않습니다 ...
galinette

끈적 거리는 걸 봐 당신이 참조로 순수하게 통과했다면, 그것은 그 주장을 바꿨을 것입니다. 아니요, 파이썬은 순수한 참조 기준 언어가 아닙니다. 값으로 참조를 전달합니다.
ncmathsadist

0

파이썬은 참조 값으로 복사됩니다. 객체는 메모리에서 필드를 차지하고 참조는 해당 객체와 연관되어 있지만 자체적으로 메모리에서 필드를 차지합니다. 이름 / 값은 참조와 연관됩니다. 파이썬 함수에서는 항상 참조 값을 복사하므로 코드에서 n은 새로운 이름으로 복사됩니다. 할당하면 호출자 스택에 새로운 공간이 있습니다. 그러나 목록의 경우 이름도 복사되었지만 동일한 메모리를 참조합니다 (목록에 새로운 값을 할당하지 않기 때문에). 그것은 파이썬의 마술입니다!


0

내 일반적인 이해는 모든 객체 변수 (예 : 목록 또는 dict 등)는 해당 기능을 통해 수정할 수 있다는 것입니다. 내가 할 수 없다고 생각하는 것은 매개 변수를 다시 할당하는 것입니다. 즉 호출 가능한 함수 내에서 참조로 지정합니다.

그것은 다른 많은 언어들과 일치합니다.

다음 짧은 스크립트를 실행하여 작동 방식을 확인하십시오.

def func1(x, l1):
    x = 5
    l1.append("nonsense")

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)

-3

나는 대답을 여러 번 수정했고 아무 말도 할 필요가 없다는 것을 깨달았습니다. 파이썬은 이미 설명했습니다.

a = 'string'
a.replace('t', '_')
print(a)
>>> 'string'

a = a.replace('t', '_')
print(a)
>>> 's_ring'

b = 100
b + 1
print(b)
>>> 100

b = b + 1
print(b)
>>> 101

def test_id(arg):
    c = id(arg)
    arg = 123
    d = id(arg)
    return

a = 'test ids'
b = id(a)
test_id(a)
e = id(a)

# b = c  = e != d
# this function do change original value
del change_like_mutable(arg):
    arg.append(1)
    arg.insert(0, 9)
    arg.remove(2)
    return

test_1 = [1, 2, 3]
change_like_mutable(test_1)



# this function doesn't 
def wont_change_like_str(arg):
     arg = [1, 2, 3]
     return


test_2 = [1, 1, 1]
wont_change_like_str(test_2)
print("Doesn't change like a imutable", test_2)

이 악마는 참조 / 값 / 변경 가능하지 않거나 인스턴스 / 네임 스페이스 또는 변수 / 목록 또는 str이 아니며, 그것은 SYNTAX, EQUAL SIGN입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.