[파이썬]함수의 매개변수를 사용할때의 주의사항

파이썬에서 함수 사용에 있어서 주의해야할 점들을 정리한다. 여기서 정리할 주의점들의 대부분은 가변형과 불변형 데이터라는 특징에 의해서 발생하는 것들이다. 즉, 파이썬 자료형의 특성들을 깊게 이해하고 있으면 발생하지 않을 문제라고 생각한다.

#1 기본적인 함수의 매개변수 사용

1
2
3
4
5
6
7
8
def sum(a, b):
a += b
return a

a = 1
b = 2
print(sum(a, b)) # 3
print(a, b) # 1 2

매우 기본적인 사용법이다.

#2 가변형 데이터를 매개변수로 사용

1
2
3
4
5
6
7
8
9
10
def sum(a, b):
a += b
return a

a = [1, 2]
print(a) # [1, 2]
b = ['a', 'b']
print(sum(a, b)) # [1, 2, 'a', 'b']
a # [1, 2, 'a', 'b']
b # ['a', 'b']

리스트는 더하기 연산을 지원하기 때문에 그에 맞은 연산 결과를 반환한다. 그리고 다시 a와 b 변수의 내용을 출력하면 a의 값이 변경된 것을 볼 수 있다.

변수 a가 변경된 이유는 값이 아닌 참조가 넘어갔기 때문이다. 이는 파이썬이 데이터를 효율적으로 사용하기 위한 조치이다. a의 데이터가 1억개 있다고 가정해보자. 함수 안에서 연산을 위해 1억개의 데이터를 모두 메모리에 올려놓고 사용한다면 리소스의 낭비가 심해질 것이다.

#3 불변형 데이터를 매개변수로 사용

1
2
3
4
5
6
7
8
9
10
def sum(a, b):
a += b
return a

a = (1, 2)
b = ('a', 'b')

print(sum(a, b)) # (1, 2, 'a', 'b')
a # (1, 2)
b # ('a', 'b')

리스트를 넘겼을때와 다르게 원본 a, b가 변하지 않았다. 튜플같이 불변형 데이터를 함수의 인자로 줄 때에는 참조를 넘겨주는 것이 아니라 실제 값을 복사해서 넘겨주기 때문이다. 값을 복사해서 넘겨주기 때문에 데이터의 크기가 크다면 퍼포먼스에 영향을 줄 수 있으므로 상황에 맞는 자료형을 사용해야 한다.

#4 참조를 반환하는 불변형

str / bytes / frozenset / tuple : 사본을 생성하지 않고 참조를 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
a = (1, 2, 3)
aa = tuple(a)
aaa = a[:]

print(a is aa) # True
print(a is aaa) # True
id(a), id(aa), id(aaa) # (4592214160, 4592214160, 4592214160)

b = 'abcd'
bb = 'abcd'

print(b is bb) # True
id(b), id(bb) # (4592243072, 4592243072)

불변형 자료형의 경우 값이 같을때 항상 같은 객체를 참조한다.


정리

데이터 타입이 가변형 / 불변형인지에 따라 특성이 달라지기 때문에 유의해서 사용해야한다. 함수의 매개변수로써 리스트가 쓰이면 참조 주소가 넘어가기 때문에 원본에도 영향을 끼치며 불변형인 튜플이 쓰이면 값 그 자체를 넘기게 된다.

[참고]
패스트캠퍼스 강의

Share

[파이썬]객체 슬라이싱과 __getitem__

파이썬의 리스트와 튜플에 를 이용해서 슬라이싱을 해보았을 것이다. 뿐만 아니라 리스트처럼 클래스의 인스턴스 자체도 슬라이싱을 할 수 있도록 만들 수 있다. 이때에 필요한 속성이 __getitem__이라는 속성이며 슬라이싱을 구현하는 방법에 대해서 정리해보자.

__getitem__

두개의 밑줄로 시작하는 파이썬의 특별 메소드 중 하나이다. 슬라이싱을 구현할 수 있도록 도우며 리스트에서 슬라이싱을 하게되면 내부적으로 __getitem__ 메소드를 실행한다는 점이 중요하다. 따라서 객체에서도 슬라이싱을 하기 위해서는 __getitem__ 메소드가 필수적이다.

코드로 살펴보는 __getitem__

1
2
3
4
5
class CustomNumbers:
def __init__(self):
self._numbers = [n for n in range(1, 11)]

a = CustomNumbers()

위의 클래스를 통해 a는 1부터 10까지 저장되어 있는 리스트를 속성으로 가진다. 만약 현재의 상태에서 리스트 슬라이싱을 하고 싶다면 _numbers 속성에 직접 접근해서 사용해야할 것이다.

1
2
a._numbers[2:5]
# [3, 4, 5]

인스턴스 변수에 직접 접근하지 말고 객체 자체를 통해서 슬라이싱을 구현하기 위해서는 **getitem 특별 메소드를 정의해야한다.** 그리고 이 함수는 인덱스를 인수로 받아야 한다.

1
2
3
4
5
6
7
8
9
10
class CustomNumbers:
def __init__(self):
self._numbers = [n for n in range(1, 11)]

def __getitem__(self, idx):
return self._numbers[idx]

a = CustomNumbers()
a[2:7]
# [3, 4, 5, 6, 7]

구현 내용은 굉장히 간단하다. 슬라이싱을 사용할 속성에 idx만 적어주면 알아서 해결해준다.

정리

슬라이싱을 구현하기 위해서 필요한 것은 __getitem__라는 특별 메소드임을 잊지말자!


[정리]
패스트캠퍼스 강의

Share

[파이썬]클래스 인스턴스의 고정 속성: __slots__

클래스를 사용해보았다면 __dict__을 통해서 클래스의 인스턴스가 가진 모든 속성을 확인해본적이 있을 것이다. 딕셔너리는 메모리를 많이 잡아먹는 단점이 있는데, 만약 수 백만개의 인스턴스를 생성한다면 그 만큼 사용되는 메모리가 매우 크게 증가한다는 것을 의미한다. 클래스가 가지는 속성을 제한함으로써 최적화를 할 수 있으며 이 때 필요한 __slot__이라는 키워드에 대해서 정리해보자.

__slots__ 이란?

slots은 클래스가 가진 속성을 제한할때 사용한다. 클래스의 __dict__ 속성을 최적화하는 데에 사용할 수 있는데 기존의 딕셔너리로 관리하는 속성을 집합 형태의 Set으로 바꿈으로써 동작한다. 메모리를 절약할 수 있다는 장점이 있다.

코드로 살펴보는 __slots__

slots를 사용하는 클래스와 그렇지 않은 클래스를 정의하고 인스턴스까지 생성한 뒤 각 인스턴스의 네임 스페이스를 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
class SlotClass:
__slots__ = ('name', )

class NoSlotClass:
pass

slot = SlotClass()
no_slot = NoSlotClass()
no_slot.name = 'a'

slot.__dict__ # 에러 - slots은 dict을 가지고 있지 않기 때문에
no_slot.__dict__

slot 인스턴스는 slots을 사용하고있기 때문에 더 이상 네임 스페이스를 __dict__을 통해서 관리하지 않는다. 메모리 사용량에 있어서 얼마나 차이가 있는지 실험을 해보자.

slots 사용 여부에 따른 메모리 사용량 비교

1
2
3
4
5
6
7
8
9
10
import timeit

def check_time(obj):
def inner():
obj.name = 'hello'
del obj.name
return inner

min(timeit.repeat(check_time(slot), number=10000000)) # 1.6706519379999918
min(timeit.repeat(check_time(no_slot), number=10000000)) # 2.139917491999995

timeit 모듈의 repeat 함수를 이용했다. 인스턴스의 name 속성에 hello라는 문자열을 저장하는 작업을 천만번 수행하도록 했고 그 중에서 가장 짧은 시간이 출력되도록 했다. 결과는 큰 차이가 발생하진 않았지만 slots를 사용하지 않았을때 시간이 더 걸리는 것으로 보아 작업량이 더 많아 메모리를 더 점유한다고 생각할 수 있겠다.

정리

파이썬 클래스가 가지는 딕셔너리 형태의 속성은 런타임에도 임의의 새로운 속성을 추가할 수 있다는 유연성을 제공해주지만 더 많은 메모리를 사용한다는 단점도 가지고 있다. 클래스 속성을 딕셔너리로 관리하는 대신에 slots을 사용하여 고정된 속성 집합을 사용하게 함으로써 메모리 사용량을 줄이고 최적화 할 수 있다.

정말 많은 객체를 생성할 거라고 예상되는 클래스는 slots으로 속성을 관리하는 것을 추천한다!!


[참고]
패스트캠퍼스 강의

Share