- Python의 변수와 참조 2023.09.21
- deque 모듈 2023.08.30
- Python - isinstance() 2023.08.12
- Python) 위치 인자, 키워드 인자 2023.07.25
- @classmethod 와 @staticmethod 2023.07.20
- @property 데코레이터 2023.07.01
변수는 일반적으로 데이터를 저장할 수 있는 메모리 공간을 의미합니다.
다만, 파이썬에서의 변수는 C, 자바 같은 컴파일 언어에서의 변수와는 차이가 있습니다.
자바의 변수
자바를 예로 들면 자바는 변수를 선언하면서 선언된 자료형 만큼의 메모리를 확보합니다. 더 정확하게 말하면 원시 타입(Primitive Type)에 해당하는 변수(int, double, boolean 등)를 선언하면 선언된 타입만큼의 크기의 영역을 Stack 메모리에 선언합니다. 그리고 객체와 메서드 같은 참조형 타입(Reference type)은 Heap 메모리에 실질적인 데이터가 저장되고 그 데이터의 주소값이 stack 메모리에 저장되는 형태입니다.
파이썬의 변수
하지만 파이썬의 변수는 다릅니다.
파이썬에서 변수는 타입에 의해 선언되는 게 아닌 객체와 연관되어 있습니다.
왜냐하면 변수에 선언되는 데이터는 모두 객체이기 때문입니다.
만약 a = 10 이라는 python 구문이 있다면, 파이썬은 우선 10을 나타내는 객체를 생성합니다. 이후 변수 a를 생성하고 변수 a와 객체 10을 연결합니다.
즉, a와 객체 10은 다른 위치에 저장되고 이 둘은 참조에 의해 연결됩니다.
변수명 참조 주소 메모리 주소 객체 값
| a | 0x1 | ➡️ | 0x1 | 10 |
사실 이 부분을 좀 더 자세히 설명하면 다음과 같습니다.
interning
파이썬에서는 정수와 같은 몇몇 흔한 값들에 대해 미리 객체를 생성하고 재사용합니다. 이를 "interning"이라고 합니다. 특히 작은 정수에 대해 이런 방식이 적용됩니다.
구체적으로, -5부터 256까지의 정수 객체는 파이썬이 시작될 때 미리 생성되어 있습니다. 따라서 a = 10 구문을 실행할 때, 새로운 10 객체를 생성하는 것이 아니라 이미 생성되어 있는 10 객체를 재사용합니다.
따라서, a = 10을 실행하면 다음과 같은 순서로 동작합니다:
- 10 객체가 이미 메모리에 있는지 확인합니다.
- 이미 있으면 그 객체의 참조를 변수 a에 연결합니다.
- 없으면 (예를 들어, 매우 큰 숫자나 5 이하의 숫자에서는) 새로운 객체를 생성하고 그 참조를 변수 a에 연결합니다.
즉, a = 10 구문을 실행하면 a는 이미 존재하는 10 객체를 참조하게 됩니다.
10과 10은 같은 객체
앞에서 말한 interning은 파이썬 구문으로 확인 가능합니다.
파이썬에서 == 는 값이 같은지 확인하고 'is'는 같은 객체를 참조하고 있는지를 확인해 줍니다.
a = 10
b = 10
a == b
#--> True
a is b
#--> True
10은 같은 객체임을 확인할 수 있습니다.
얕은 복사, 깊은 복사
list_1 = [1,2,3]
list_2 = list_1
list_1[0] = 10
list_2
#--> [10,2,3]
list_1과 list_2는 같은 [1,2,3] 객체를 참조합니다.
그리고 list는 가변 객체이기에 값이 변할 수 있습니다.
List_1[0] = 10 이 구문은 list_1이 다른 객체를 참조하도록 하는 구문이 아니라, list_1이 참조하는 객체 자체를 바꾼 것입니다.
그러기에 같은 객체를 참조하는 list_2의 출력값도 바뀌는 것입니다.
표로 표현하면 다음과 같습니다.
변수명 참조 주소 메모리 주소 객체 값
| list_1 | 0x1 | ➡️ | 0x1 | [1,2,3] |
| list_2 | 0x1 | ↗️ |
list_1[0] = 10 구문 실행
변수명 참조 주소 메모리 주소 객체 값
| list_1 | 0x1 | ➡️ | 0x1 | [10,2,3] |
| list_2 | 0x1 | ↗️ |
다른 예시를 더 살펴보면
동적 타입 언어(dynamic typing)
a = 10
a = "ten"
이 경우 a의 타입이 정수타입에서 문자열타입으로 변경된 것이 아니라 변수 a가 객체 10을 참조했다가, 객체 "ten"을 참조하는 것이다.
왜냐하면 파이썬에서의 변수는 동적으로 타입을 갖습니다. 파이썬의 변수는 참조하는 객체의 타입에 따라 동적으로 타입이 바뀔 수 있습니다.
변수는 사실상 특정 객체를 참조하고 있기에 가능한 것입니다. 자바나, c에서는 불가능합니다. 왜냐하면 변수의 타입에 따라 메모리의 크기를 정했기 때문입니다.
객체 메모리 구성
파이썬의 객체는 메모리 상에서 여러 부분으로 구성되어 있습니다. 헤더에는 타입 정보와 다른 메타데이터가 저장되며, 이후에 실제 데이터 값 등이 저장됩니다.
객체는 타입 정보를 헤더에 저장합니다. 그래서 파이썬에서는 변수에 타입을 지정할 필요가 없습니다. 왜냐하면 각 객체가 자신의 타입 정보를 직접 포함하고 있기 때문입니다. 이 특징 덕분에 앞에서 말한 파이썬이 동적 타이핑 언어가 가능한 이유입니다.
객체의 메모리 구성
| 타입 정보 (헤더) (Type Information) |
| 참조 카운트 (Reference Count) |
| 객체의 실제 데이터 값 (Object’s Actual Data Value) |
참조 카운트
참조 카운트는 파이썬의 메모리 관리 메커니즘 중 하나로, 특정 객체에 대한 참조의 수를 나타냅니다.
파이썬에서는 객체의 메모리를 효율적으로 관리하기 위해 참조 카운팅 방식을 사용합니다. 객체가 생성될 때 해당 객체의 참조 카운트는 1로 설정됩니다. 만약 다른 변수나 데이터 구조가 그 객체를 참조하게 되면 참조 카운트가 증가하며, 참조가 사라질 때마다 참조 카운트가 감소합니다.
참조 카운트가 0이 되면, 해당 객체는 더 이상 어떤 변수나 데이터 구조에도 참조되지 않는 것을 의미합니다. 이 시점에서 파이썬의 가비지 컬렉터(garbage collector)는 해당 객체의 메모리를 해제합니다.
예
a = [1, 2, 3] # 리스트 객체 생성, 참조 카운트 = 1
b = a # 리스트 객체에 대한 참조 추가, 참조 카운트 = 2
del a # 리스트 객체에 대한 참조 제거, 참조 카운트 = 1
del b # 리스트 객체에 대한 참조 제거, 참조 카운트 = 0 (이제 객체의 메모리가 해제될 수 있음)
'Python > Python' 카테고리의 다른 글
| deque 모듈 (0) | 2023.08.30 |
|---|---|
| Python - isinstance() (0) | 2023.08.12 |
| Python) 위치 인자, 키워드 인자 (0) | 2023.07.25 |
| @classmethod 와 @staticmethod (0) | 2023.07.20 |
| @property 데코레이터 (0) | 2023.07.01 |
deque 모듈
deque(데크, double-ended queue) 모듈은 양쪽 끝에서 삽입과 삭제가 가능한 자료구조입니다.
즉, 스택과 큐를 모드 지원하는 모듈로 Python의 collections 모듈에서 제공됩니다.
deque를 사용하기 위해서는 리스트와 비슷한 형식으로 데이터를 저장해야 합니다.
기본적인 사용법
Import 방법
from collections import deque
생성
d = deque([1, 2, 3, 4])
삽입 / 삭제
for i in range(5):
deque_list.append(i) # 오른쪽 끝에 삽입
print(deque_list)
# deque([0, 1, 2, 3, 4])
deque_list.pop() # 오른쪽 끝 요소 삭제
print(deque_list)
# deque([0, 1, 2, 3])
for i in range(5):
deque_list.appendleft(i) # 왼쪽 끝에 삽입
print(deque_list)
# deque([4, 3, 2, 1, 0])
deque_list.popleft() # 왼쪽 끝 요소 삭제
print(deque_list)
# deque([3, 2, 1, 0])
조회
d[0] # 첫 번째 요소 조회
d[-1] # 마지막 요소 조회
길이
len(d) # deque의 길이
장점 및 특징
deque 모듈은 리스트에 비해 일반적인 스택과 큐 연산에 더 효율적입니다.
또한 양쪽에서의 삽입, 삭제가 O(1) 시간 복잡도로도 가능합니다. 이로 얻는 장점은 아래 예제에서 확인하겠지만, 리스트에 비해 삽입, 삭제 연산이 매우 빠릅니다.
이러한 장점을 갖는 이유는 deque가 연결 리스트(Linked list)의 특성을 지원하기 때문입니다.
참고로 연결 리스트는 데이터를 저장할 때 요소의 값을 한 쪽으로 연결한 후, 요소의 다음 값의 주소값을 저장하여 데이터를 연결하는 기법입니다.

연결 리스트는 다음 요소의 주소값을 저장하므로 데이터를 원형으로 저장할 수 있고, 마지막 요소에 첫 번째 값의 주소를 저장한다면 해당 값을 찾아갈 수 있습니다. 이러한 특징 때문에 가능한 기능 중 하나가 rotate() 함수입니다. rotate(n)은 요소들을 n값 만큼 회전하는 함수입니다. n이 양수이면 오른쪽으로 회전하고, n이 음수이면 왼쪽으로 회전합니다.
from collections import deque
deque_list = deque([2, 4, 6, 8])
print(deque_list)
# deque([2, 4, 6, 8])
deque_list.rotate(1)
print(deque_list)
# deque([8, 2, 4, 6])
deque_list.rotate(-1)
print(deque_list)
# deque([2, 4, 6, 8])
rotate() 함수는 이처럼 각 요소와 인덱스 번호를 하나씩 옮깁니다.
deque모듈의 추가적인 함수들
reversed()
reversed()함수는 기존과 반대로 데이터를 저장할 수 있습니다.
deque_list = deque([1, 2, 3, 4])
reversed_deque = deque(reversed(deque_list))
print(reversed_deque)
# deque([4, 3, 2, 1])
clear()
deque리스트를 모두 지웁니다.
deque_list = deque()
for i in range(4):
deque_list.append(i)
print(deque_list)
# deque([0, 1, 2, 3])
deque_list.clear()
print(deque_list)
# deque([])
extend(), extendleft()
extend()와 extedleft()는 리스트가 통째로 추가됩니다.
deque_list = deque([1, 2, 3])
deque_list.extend([4, 5, 6])
print(deque_list)
# deque([1, 2, 3, 4, 5, 6])
deque_list.extendleft([0, -1, -2])
print(deque_list)
deque([-2, -1, 0, 1, 2, 3, 4, 5, 6])
maxlen
maxlen은 deque의 매개변수로 deque의 사이즈를 고정하는 인자값입니다.
data = [1, 2, 3, 4, 5, 6, 7]
deque_list = deque(data, maxlen=3)
print(deque_list)
# deque([5, 6, 7], maxlen=3)
deque와 list의 성능 테스트 비교
from collections import deque
import time
# insert 성능 비교
# deque
deque_list = deque()
start = time.time() # second단위
for i in range(100000):
deque_list.insert(1, "x")
end = time.time()
print("deque insert 시간: ", end - start)
# deque insert 시간: 0.012130975723266602
# list
list = list()
start = time.time()
for i in range(100000):
list.insert(1, "x")
end = time.time()
print("list insert 시간: ", end - start)
# list insert 시간: 4.031864166259766
# 추출 성능 비교
# deque start = time.time()
for i in range(100000):
deque_list.pop()
end = time.time()
print("deque pop 시간: ", end - start)
# deque pop 시간: 0.0071468353271484375
# list
start = time.time()
for i in range(100000):
list.pop()
end = time.time()
print("list pop 시간: ", end - start)
# list pop 시간: 0.007027149200439453
# 아래 부분을 테스트 하기 위해서는 pop 부분 코드를 주석 처리해야 합니다.
# deque
start = time.time()
for i in range(100000):
deque_list.popleft()
end = time.time()
print("deque pop 시간: ", end - start)
# deque pop 시간: 0.00786733627319336
# list
start = time.time()
for i in range(100000):
list.pop(0)
end = time.time()
print("list pop 시간: ", end - start)
# list pop 시간: 1.268723964691162
이를 통해 deque가 list에 비해 빠르다는 것을 알 수 있습니다.
'Python > Python' 카테고리의 다른 글
| Python의 변수와 참조 (0) | 2023.09.21 |
|---|---|
| Python - isinstance() (0) | 2023.08.12 |
| Python) 위치 인자, 키워드 인자 (0) | 2023.07.25 |
| @classmethod 와 @staticmethod (0) | 2023.07.20 |
| @property 데코레이터 (0) | 2023.07.01 |
Python - isinstance()
isinstance() 함수는 Python 내장 함수 중 하나로, 어떤 객체가 특정 클래스나 타입의 인스턴스인지를 확인하는 데 사용됩니다. 주어진 객체가 지정한 클래스 또는 타입의 인스턴스인 경우 True를 반환하고, 아닌 경우 False를 반환합니다.
예
# 예제 클래스 정의
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
# 객체 생성
dog_instance = Dog()
cat_instance = Cat()
# isinstance 사용
print(isinstance(dog_instance, Dog)) # True
print(isinstance(cat_instance, Dog)) # False
# 상속 관계 확인
print(isinstance(dog_instance, Animal)) # True
print(isinstance(cat_instance, Animal)) # True
위의 예시에서 isinstance() 함수는 객체가 특정 클래스의 인스턴스인지 확인합니다. 또한 클래스 간의 상속 관계에도 사용할 수 있습니다.
활용
제가 Django 프로젝트에서 활용한 방식을 간단하게 표현하면 다음과 같습니다.
class PDFMaker:
# 생략
def makes_pdf(self) -> Union[HttpResponse, bytes]:
try:
# 생략
# bytes 타입 return
except Exception as e:
# 생략
return HttpResponse(response_data, status=500)
# views
class PDFView(APIVIew):
# 생략
pdf_maker = PDFMaker()
byte = pdf_maker.makes_pdf()
if isinstance(byte, HttpResponse):
return byte # 예외 발생 시 HttpResponse 반환
# 생략
return FileResponse(byte)
예시 코드에서 사용된 isinstance() 함수의 목적은 byte 변수가 HttpResponse 클래스의 인스턴스인지를 확인하는 것입니다.
이를 통해 pdf_maker.makes_pdf() 메서드가 HttpResponse 객체를 반환하는지 여부를 판단하여 에러 처리를 수행했습니다.
주어진 코드에서 isinstance(byte, HttpResponse)는 byte 변수가 HttpResponse 클래스의 인스턴스인지를 확인하고, 그에 따라 에러 처리를 수행하는 역할을 합니다.
'Python > Python' 카테고리의 다른 글
| Python의 변수와 참조 (0) | 2023.09.21 |
|---|---|
| deque 모듈 (0) | 2023.08.30 |
| Python) 위치 인자, 키워드 인자 (0) | 2023.07.25 |
| @classmethod 와 @staticmethod (0) | 2023.07.20 |
| @property 데코레이터 (0) | 2023.07.01 |
파이썬에서 함수를 호출할 때, 인자(argument)를 전달하는 방법에는 두 가지가 있습니다. 위치인자(Positional Arguments)와 키워드 인자(Keyword Arguments)로 나뉩니다.
1. 위치 인자 (Positional Arguments)
위치 인자는 함수 호출 시 전달하는 인자가 함수 정의에서 매개변수(parameter)의 위치에 따라 매핑되는 방식입니다. 즉, 인자의 순서가 함수 정의에서 매개변수의 순서와 일치해야 합니다. 위치인자를 사용할 때는 인자의 값을 순서대로 전달합니다.
일반적으로 인자를 정의하는 방식입니다.
예
def greet(name, age):
print(f"Hello, {name}. You are {age} years old.")
이 함수는 name과 age라는 두 개의 매개변수를 받아서 인사말과 나이를 출력합니다. 이제 이 함수를 호출할 때 위치인자를 사용하면 다음과 같습니다.
greet("Alice", 30)
위의 호출은 name에 "Alice"라는 값이, 그리고 age에 30이라는 값이 매핑되어 함수가 실행됩니다.
2. 키워드 인자 (Keyword Arguments)
키워드 인자는 함수 호출 시 인자의 이름을 지정하여 함수 정의에서 해당 이름과 매핑되도록 전달하는 방식입니다. 이를 통해 인자의 순서를 신경 쓰지 않고 명확하게 어떤 값이 어떤 매개변수에 해당하는지 지정할 수 있습니다.
예
greet(age=30, name="Alice")
이렇게 키워드 인자를 사용하면 인자의 순서와 상관없이 name과 age에 대해 명확하게 지정할 수 있습니다. 파이썬에서는 위치인자와 키워드 인자를 혼합하여 사용할 수도 있습니다. 하지만 키워드 인자를 사용하려면 위치인자가 먼저 나와야 합니다.
greet("Bob", age=25)
위의 호출은 name에 "Bob"이라는 위치 인자가 먼저 왔고, age에는 키워드 인자로 25가 전달되었습니다.
하지만, 이렇게 위치 인자와 키워드 인자를 혼합해서 사용하는 방식은 지양하는 것이 좋습니다.
'Python > Python' 카테고리의 다른 글
| deque 모듈 (0) | 2023.08.30 |
|---|---|
| Python - isinstance() (0) | 2023.08.12 |
| @classmethod 와 @staticmethod (0) | 2023.07.20 |
| @property 데코레이터 (0) | 2023.07.01 |
| glob.glob() 함수 (0) | 2023.06.11 |
인스턴스 메서드, 클래스 메서드, 정적 메서드 차이점
- 인스턴스 메서드(Instance Methods): 이 메서드들은 클래스의 객체 인스턴스에서 호출되며, 첫 번째 인자로 인스턴스 자체(
self)를 받습니다. 인스턴스 변수에 접근하여 값을 읽거나 수정할 수 있습니다.
클래스 메서드(Class Methods): 클래스 메서드는 클래스에서 호출되며, 첫 번째 인자로 클래스(
cls)를 받습니다. 이 메서드들은 클래스 변수를 수정하는 데 사용될 수 있습니다. 인스턴스에 대한 정보는 사용할 수 없습니다.정적 메서드(Static Methods):
@staticmethod데코레이터로 표시된 메서드는 인스턴스나 클래스에 대한 참조 없이 동작합니다. 이 메서드들은 클래스나 인스턴스 상태를 수정할 필요가 없는 경우에 사용됩니다.
@classmethod
@classmethod 데코레이터는 메서드를 클래스 메서드로 표시하는 데 사용됩니다. 일반적인 인스턴스 메서드와 달리 클래스 메서드는 클래스 자체를 첫 번째 인자로 받습니다(일반적으로 cls로 표시됩니다), 그리고 객체 인스턴스가 아닌 클래스를 조작합니다.
예시
class MyClass:
class_variable = "I am a class variable"
def __init__(self, instance_variable):
self.instance_variable = instance_variable
def instance_method(self):
print(f"Instance method, instance variable: {self.instance_variable}")
@classmethod
def class_method(cls):
print(f"Class method, class variable: {cls.class_variable}")
@staticmethod
def static_method():
print("Static method, no access to class or instance variables")
# 인스턴스 생성
my_instance = MyClass("I am an instance variable")
# 인스턴스 메서드 호출
my_instance.instance_method()
# Instance method, instance variable: I am an instance variable
# 클래스 메서드 호출
MyClass.class_method()
# Class method, class variable: I am a class variable
# 정적 메서드 호출
MyClass.static_method()
# Static method, no access to class or instance variables
@classmethod는 주로 클래스 변수를 조작하거나, 클래스에 대한 상태를 변경할 때 사용됩니다. 또한, 인스턴스를 생성하지 않고도 클래스의 기능을 사용할 수 있게 해줍니다.
@staticmethod
@staticmethod 데코레이터는 메서드를 정적(static) 메서드로 지정합니다. 정적 메서드는 클래스와 관련이 있지만 클래스나 인스턴스의 상태에 영향을 받지 않으며, 인스턴스 메서드와 달리 self나 cls와 같은 첫 번째 매개변수를 갖지 않습니다.
따라서 @staticmethod를 사용하여 정의된 메서드는 클래스의 인스턴스 없이도 호출할 수 있으며, 클래스 내부의 다른 메서드나 속성에 접근할 수 있습니다.
아래 예시 코드에서 @staticmethod를 사용한 이유는 Serializer 클래스의 serialize() 메서드를 클래스 메서드로 정의하고자 했기 때문입니다. @staticmethod를 사용하면 serialize() 메서드를 인스턴스화하지 않고도 직접 호출할 수 있으며, 클래스 내부의 다른 메서드나 속성에 접근할 수 있습니다.
class UserSerializer:
@staticmethod
def serialize(user):
# 직렬화 로직
return serialized_data
이렇게 하면 UserSerializer.serialize(user)와 같은 형태로 메서드를 호출할 수 있습니다.
정적 메서드는 클래스의 상태와 관련이 없는 유틸리티 메서드를 정의하거나, 클래스와 밀접한 연관이 있는 로직을 모듈화하고 싶을 때 유용하게 사용될 수 있습니다.
'Python > Python' 카테고리의 다른 글
| Python - isinstance() (0) | 2023.08.12 |
|---|---|
| Python) 위치 인자, 키워드 인자 (0) | 2023.07.25 |
| @property 데코레이터 (0) | 2023.07.01 |
| glob.glob() 함수 (0) | 2023.06.11 |
| Python 라이브러리 - functools (0) | 2023.05.24 |
Python에서 @property 데코레이터는 메서드를 클래스의 속성처럼 접근할 수 있게 해줍니다. 이를 사용하면, 메서드를 호출하는 것처럼 보이지 않고 속성에 접근하는 것처럼 코드를 작성할 수 있습니다. 이러한 방식은 파이썬스러운(pythonic) 방식으로 디자인할 수 있도록 한다고 합니다.
예제
예를 들어, 다음과 같은 클래스를 정의합니다.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@property
def diameter(self):
return self._radius * 2
def area(self):
return 3.14 * self._radius ** 2
이 클래스에는 radius, diameter, 그리고 area라는 세 가지 메서드가 있습니다. radius와 diameter에는 @property 데코레이터가 붙어있고, area에는 붙어있지 않습니다.
이제 이 클래스의 인스턴스를 생성하고, 메서드와 속성에 접근하면 아래와 같습니다.
c = Circle(5)
# @property가 붙은 메서드는 속성처럼 접근합니다.
print(c.radius) # 5 출력
print(c.diameter) # 10 출력
# @property가 붙지 않은 메서드는 함수처럼 호출해야 합니다.
print(c.area()) # 78.5 출력
@property 데코레이터가 붙은 메서드는 소괄호를 사용하지 않고 접근할 수 있습니다. 반면에, @property가 없는 메서드는 함수처럼 호출해야 합니다(소괄호를 사용해야 합니다).
@property를 사용하는 경우는 주로, 인스턴스의 상태를 나타내는 값을 계산해서 제공할 때 사용됩니다. 이렇게 하면 클라이언트 코드가 해당 객체의 내부 구현에 대해 알 필요 없이, 일관된 방식으로 속성에 접근할 수 있습니다.
'Python > Python' 카테고리의 다른 글
| Python) 위치 인자, 키워드 인자 (0) | 2023.07.25 |
|---|---|
| @classmethod 와 @staticmethod (0) | 2023.07.20 |
| glob.glob() 함수 (0) | 2023.06.11 |
| Python 라이브러리 - functools (0) | 2023.05.24 |
| Python 라이브러리 - itertools (0) | 2023.05.20 |