jjiiiinn 2024. 7. 10. 10:05
728x90

파이썬의 클래스

  1. 기본 개념:
    • 클래스는 데이터와 기능을 함께 묶는 방법
    • 새 클래스 생성은 새로운 객체 타입 생성
    • 클래스 인스턴스는 자신만의 속성을 가질 수 있음
  2. 파이썬 클래스의 특징:
    • 최소한의 새로운 문법으로 구현
    • 다중 상속 지원
    • 메서드 오버라이딩 가능
    • 모든 표준 객체 지향 프로그래밍 기능 제공
  3. 파이썬 클래스의 특별한 점:
    • 대부분의 멤버가 public
    • 모든 메서드가 virtual (C++ 용어로)
    • 내장 타입도 상속 가능
    • 연산자 오버로딩 지원
    • 동적 생성 및 수정 가능
  4. 객체와 이름의 관계 (Aliasing):
    • 여러 이름이 같은 객체를 참조할 수 있음
    • 가변 객체에서 중요한 개념
    • 함수에 객체 전달 시 효율적 (포인터와 유사)
  5. 주요 특징:
    • 클래스 자체도 객체
    • 임포팅과 이름 변경 지원
    • 내장 연산자들을 클래스 인스턴스에 대해 재정의 가능

파이썬 스코프와 이름 공간

  1. 이름 공간 (Namespace)
    • 정의: 이름에서 객체로의 매핑
    • 구현: 대부분 파이썬 딕셔너리로 구현됨
    • 종류: a) 내장 이름 공간: 내장 함수, 예외 등 b) 모듈의 전역 이름 공간 c) 함수 호출의 지역 이름 공간 d) 객체의 속성
    • 특징: 서로 다른 이름 공간의 이름들 간에는 관계가 없음
  2. 속성 (Attributes)
    • 정의: 점(.) 뒤에 오는 모든 이름
    • 예: z.real에서 real은 객체 z의 속성
    • 모듈 속성: 읽기/쓰기 가능, 삭제 가능 (del 사용)
  3. 스코프 (Scope)
    • 정의: 이름 공간에 직접 접근 가능한 프로그램의 텍스트 영역
    • 특징: 정적으로 결정되지만 동적으로 사용됨
    • 종류 (검색 순서):
      • a) 지역 스코프 (지역 스코프)
      • b) 둘러싸는 함수의 스코프 (그 함수를 감싸고 있는 함수)
      • c) 전역 스코프 (현재 모듈)
      • d) 내장 이름 스코프 (내장 스코프)
  4. 이름 공간의 생성과 수명
    • 내장 이름: 인터프리터 시작 시 생성, 영구 유지
    • 모듈 전역 이름: 모듈 정의 읽을 때 생성, 인터프리터 종료 시까지 유지
    • 함수 지역 이름: 함수 호출 시 생성, 함수 종료 시 삭제
    • 최상위 호출 문장: __main__ 모듈의 일부로 취급
  5. 이름 바인딩 규칙
    • 기본: 대입 연산은 가장 내부 스코프에 새 이름 생성
    • global 문: 변수를 전역 스코프에 연결
    • nonlocal 문: 변수를 둘러싸는 스코프에 연결
    • 함수 정의와 import 문: 지역 스코프에 이름 연결
  6. 특별한 특징
    • 스코프는 텍스트적으로 결정되나, 이름 검색은 실행 시 동적으로 수행
    • 지역 변수는 이미 정적으로 결정됨
    • 모듈의 속성과 전역 이름은 같은 이름 공간 공유
    • 대입 연산은 데이터를 복사하지 않고, 이름을 객체에 연결
x = 10  # 전역 변수

def func():
    x = 20  # 지역 변수
    print("함수 안의 x:", x)

func()
print("함수 밖의 x:", x)

스코프와 이름 공간 예

  • 주요 포인트:
    • 지역 변수는 해당 함수 내에서만 영향을 미칩니다.
    • nonlocal은 가장 가까운 외부 함수의 변수를 참조합니다.
    • global은 모듈 수준의 변수를 참조하거나 생성합니다.
    • 각 스코프는 독립적이며, 명시적으로 지정하지 않으면 외부 스코프의 변수에 영향을 주지 않습니다.
def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"

    do_local()
    print("After local assignment:", spam)

    do_nonlocal()
    print("After nonlocal assignment:", spam)

    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

출력

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

클래스 정의 문법

  1. 클래스의 기본 구조:
    • 'class' 키워드로 시작
    • 클래스 이름 지정 (대문자로 시작하는 것이 관례)
    • 콜론(:) 사용
    • 들여쓰기로 클래스 내용 구분

예시:

class MyFirstClass:
    # 클래스 내용
  1. 클래스의 특징:
    • 새로운 타입의 객체를 만드는 방법
    • 함수처럼 먼저 정의되어야 사용 가능
    • 클래스 안에는 주로 함수들이 정의됨 (이를 '메서드'라고 부름)
  2. 클래스 정의 시 일어나는 일:
    • 새로운 이름 공간(namespace) 생성
    • 클래스 내의 모든 이름(변수, 함수 등)은 이 공간에 저장
  3. 클래스 정의가 끝나면:
    • 클래스 객체가 생성됨
    • 이 객체는 클래스 이름에 연결됨

클래스 객체

  • 클래스 객체의 기본 개념:
    • 클래스를 정의하면 클래스 객체가 생성됩니다.
    • 클래스 객체는 두 가지 주요 기능을 합니다:
      • a) 속성(attribute) 참조
      • b) 인스턴스 생성
  • 속성 참조:
    • 문법: 클래스이름.속성이름
    • 예: MyClass.i (변수 참조), MyClass.f (함수 참조)
    • 클래스 속성은 변경 가능합니다.
    • __doc__은 클래스의 문서 문자열을 반환하는 특별한 속성입니다.
  • 인스턴스 생성:
    • 문법: 클래스이름()
    • 예: x = MyClass()
    • 이렇게 하면 클래스의 새로운 인스턴스가 생성됩니다.
  • init 메서드:
    • 클래스의 특별한 메서드로, 인스턴스를 초기화합니다.
    • 인스턴스가 생성될 때 자동으로 호출됩니다.
    • self는 생성된 인스턴스 자체를 가리킵니다.
def __init__(self):
    self.data = []
  • 인자를 받는 init:
    • init 메서드는 추가 인자를 받을 수 있습니다.
class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

x = Complex(3.0, -4.5)

인스턴스 객체

  1. 인스턴스 객체의 기본 개념:
    • 클래스의 실제 예시(instance)입니다.
    • 클래스를 바탕으로 만들어진 구체적인 객체입니다.
  2. 인스턴스 객체의 주요 기능:
    • 속성 참조: 두 가지 종류의 속성이 있습니다.
      • a) 데이터 속성
      • b) 메서드
  3. 데이터 속성 (Data Attributes):
    • 인스턴스 변수라고도 불립니다.
    • 선언 없이 바로 사용 가능합니다.
    • 예시:
x = MyClass()
x.counter = 1  # 데이터 속성 생성
print(x.counter)  # 데이터 속성 사용
del x.counter  # 데이터 속성 삭제
  1. 메서드 (Methods):
    • 객체에 "속한" 함수입니다.
    • 클래스에서 정의된 함수가 인스턴스의 메서드가 됩니다.
    • 예시:
class MyClass:
    def f(self):
        return 'hello world'

x = MyClass()
print(x.f())  # 메서드 호출
  1. 메서드와 함수의 차이:
    • x.f는 메서드 객체입니다.
    • MyClass.f는 함수 객체입니다.
    • 메서드는 인스턴스에 연결된 함수입니다.

메서드 객체

  1. 메서드 객체의 기본 개념:

    • 메서드는 인스턴스에 연결된 함수입니다.
    • 메서드 객체는 즉시 호출하지 않고 저장해 두었다가 나중에 호출할 수 있습니다.
  2. 메서드 호출 방식:

    • 일반적인 호출: x.f()

    • 메서드 객체 저장 후 호출:

      xf = x.f
      xf()
  3. 메서드 호출 시 일어나는 일:

    • 인스턴스 객체가 자동으로 첫 번째 인자로 전달됩니다.
    • x.f()는 내부적으로 MyClass.f(x)와 동일합니다.
  4. 인자 전달:

    • 메서드 정의에 self 외의 인자가 있더라도, 호출 시 첫 번째 인자(self)는 자동으로 전달됩니다.
    • 추가 인자는 그 뒤에 전달됩니다.
  5. 메서드 작동 과정:

    • 인스턴스의 속성을 참조할 때, 해당 클래스에서 그 이름을 찾습니다.
    • 찾은 이름이 함수 객체라면, 인스턴스 객체와 함수 객체를 함께 메서드 객체로 묶습니다.
    • 메서드 호출 시, 인스턴스 객체와 전달된 인자 목록으로 새 인자 목록을 만들어 함수를 호출합니다.

클래스 변수, 인스턴스 변수

  1. 클래스 변수:

    • 클래스의 모든 인스턴스가 공유하는 변수
    • 클래스 내부에서 정의되지만, 메서드 밖에 위치
    • 예:
      class Dog:
      kind = 'canine'  # 클래스 변수
  2. 인스턴스 변수:

    • 각 인스턴스마다 고유한 변수
    • 보통 __init__ 메서드 내에서 self.변수명으로 정의
    • 예:
def __init__(self, name):
    self.name = name  # 인스턴스 변수
  1. 클래스 변수 vs 인스턴스 변수:
    • 클래스 변수는 모든 인스턴스에서 같은 값을 공유
    • 인스턴스 변수는 각 인스턴스마다 다른 값을 가질 수 있음
  2. 주의사항:
    • 가변 객체(리스트, 딕셔너리 등)를 클래스 변수로 사용할 때 주의 필요
    • 의도치 않게 모든 인스턴스에서 공유될 수 있음
  3. 올바른 사용 예:
class Dog:
    def __init__(self, name):
        self.name = name
        self.tricks = []  # 각 개에 대해 새 리스트 생성

    def add_trick(self, trick):
        self.tricks.append(trick)

기타 주의사항들

  1. 속성 우선순위:
    • 인스턴스 속성이 클래스 속성보다 우선합니다.
    • 같은 이름의 속성이 있으면 인스턴스 속성이 사용됩니다.
  2. 데이터 은닉:
    • 파이썬은 완전한 데이터 은닉을 강제하지 않습니다.
    • 관례에 따라 데이터 접근을 제어합니다.
  3. 메서드 내에서의 속성 참조:
    • 항상 self를 사용하여 인스턴스 속성을 참조합니다.
    • 이는 지역 변수와 인스턴스 변수를 구분하는 데 도움이 됩니다.
  4. self 사용:
    • 메서드의 첫 번째 매개변수로 self를 사용하는 것은 관례입니다.
    • 다른 이름을 사용할 수 있지만, self를 사용하는 것이 좋습니다.
  5. 메서드 정의:
    • 클래스 내부에 정의된 함수는 모두 메서드가 됩니다.
    • 클래스 외부에서 정의한 함수도 클래스 속성으로 할당하면 메서드가 될 수 있습니다.
  6. 메서드 간 호출:
    • 한 메서드에서 다른 메서드를 호출할 때는 self를 사용합니다.
  7. 전역 스코프:
    • 메서드는 전역 이름을 참조할 수 있지만, 가급적 사용을 피하는 것이 좋습니다.
    • 클래스는 전역 스코프로 사용되지 않습니다.
  8. 객체와 클래스:
    • 모든 값은 객체이며, 각 객체는 클래스(타입)를 가집니다.
    • 객체의 클래스는 object.__class__에 저장됩니다.

상속

  1. 상속의 기본 개념:
    • 기존 클래스(부모 클래스)의 특성을 새로운 클래스(자식 클래스)가 물려받는 것
    • 코드 재사용과 계층 구조 생성에 유용
  2. 상속 문법:
class 자식클래스(부모클래스):
    # 자식 클래스 내용
  1. 상속의 주요 특징:
    • 자식 클래스는 부모 클래스의 모든 속성과 메서드를 상속받음
    • 자식 클래스에서 부모 클래스의 메서드를 재정의(오버라이딩) 할 수 있음
    • 다중 상속 가능 (여러 부모 클래스로부터 상속 받을 수 있음)
  2. 메서드 재정의:
    • 자식 클래스에서 부모 클래스의 메서드를 같은 이름으로 다시 정의
    • 부모 클래스 메서드 호출: 부모클래스.메서드(self, 인자들)
      • super() 함수를 사용하여 부모 클래스의 메서드를 쉽게 호출할 수 있음
  3. 속성 및 메서드 검색 순서:
    • 자식 클래스에서 시작해 부모 클래스로 올라가며 검색
    • 여러 부모 클래스가 있는 경우, 정의된 순서대로 검색
  4. 유용한 내장 함수:
    • isinstance(객체, 클래스): 객체가 해당 클래스의 인스턴스인지 확인
    • issubclass(클래스A, 클래스B): 클래스A가 클래스B의 서브클래스인지 확인

다중 상속

  1. 다중 상속의 기본 개념:
    • 한 클래스가 여러 부모 클래스로부터 속성과 메서드를 상속받는 것
    • 문법: class 자식클래스(부모클래스1, 부모클래스2, ...):
  2. 속성 검색 순서 (간단한 경우):
    • 깊이 우선, 왼쪽에서 오른쪽으로 검색
    • 중복 검색 방지
  3. 메서드 결정 순서 (MRO: Method Resolution Order):
    • 실제로는 더 복잡한 동적 순서 결정 방식 사용
    • super() 함수를 통한 협력적 메서드 호출 지원
  4. 다이아몬드 관계:
    • 한 클래스가 여러 경로로 같은 부모 클래스에 접근할 수 있는 상황
    • 모든 클래스는 object를 상속받기 때문에 다중 상속에서 항상 발생
  5. 동적 순서 결정의 특징:
    • 왼쪽에서 오른쪽 순서 유지
    • 각 부모 클래스를 한 번만 호출
    • 단조성 유지 (서브클래스 생성이 부모 클래스의 우선순위에 영향을 주지 않음)

비공개 변수

  1. 진정한 비공개 변수의 부재:
    • 파이썬에는 완전히 비공개인 인스턴스 변수가 없습니다.
    • 대신 이름 규칙과 이름 뒤섞기(name mangling) 메커니즘을 사용합니다.
  2. 이름 규칙:
    • 밑줄로 시작하는 이름 (예: _variable)은 비공개로 간주됩니다.
    • 이는 규약일 뿐, 실제로 접근을 막지는 않습니다.
  3. 이름 뒤섞기 (Name Mangling):
    • 두 개의 밑줄로 시작하는 이름 (예: __variable)
    • 자동으로 _클래스이름__variable로 변환됩니다.
    • 클래스 외부에서 직접 접근을 어렵게 만듭니다.
  4. 이름 뒤섞기의 목적:
    • 서브클래스에서의 이름 충돌 방지
    • 클래스 내부 메서드 호출에는 영향을 주지 않음
  5. 예시:
class MyClass:
    def __init__(self):
        self.__private_var = 42

obj = MyClass()
print(obj._MyClass__private_var)  # 42 출력
  1. 주의사항:
    • 이름 뒤섞기는 완전한 보안을 제공하지 않습니다.
    • 여전히 변수에 접근하고 수정할 수 있습니다.
    • exec(), eval() 함수에서는 이름 뒤섞기가 적용되지 않습니다.
class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)
  • 이름 뒤섞기는 클래스 내부의 메서드 호출을 방해하지 않고 서브 클래스들이 메서드를 재정의할 수 있도록 하는 데 도움을 줍니다.
  • MappingSubclass__update 식별자를 도입하더라도 작동합니다. Mapping 클래스에서는 _Mapping__updateMappingSubclass 클래스에서는 _MappingSubclass__update로 각각 대체 되기 때문

잡동사니

  1. 데이터클래스 (Dataclasses):
    • 여러 데이터 항목을 하나로 묶는 데 유용한 기능
    • Pascal의 "레코드"나 C의 "구조체"와 유사한 개념
    • dataclasses 모듈을 사용하여 쉽게 구현 가능
from dataclasses import dataclass

@dataclass
class Employee:
    name: str
    dept: str
    salary: int

john = Employee('john', 'computer lab', 1000)
print(john.dept)  # 'computer lab' 출력
  1. 덕 타이핑 (Duck Typing) :
    • 특정 추상 데이터 타입을 기대하는 코드에 해당 메서드를 구현한 클래스를 전달할 수 있음
    • 예: 파일 객체 대신 문자열 버퍼로부터 데이터를 읽는 클래스 사용 가능
  2. 인스턴스 메서드 객체의 속성:
    • m.__self__: 메서드 m()이 속한 인스턴스 객체
    • m.__func__: 메서드에 해당하는 함수 객체

이러한 개념들은 파이썬의 유연성과 강력함을 보여줍니다:

  1. 데이터클래스를 사용하면 간단한 데이터 구조를 쉽게 만들 수 있어, 코드를 더 간결하고 명확하게 만들 수 있습니다.
  2. 덕 타이핑은 파이썬의 동적 특성을 잘 보여주며, 인터페이스에 집중하게 해줍니다. 이는 코드의 재사용성과 유연성을 높입니다.
  3. 인스턴스 메서드 객체의 특별한 속성들은 파이썬의 내부 작동 방식을 이해하는 데 도움이 됩니다.

이터레이터(Iterator)

  1. 이터레이터의 기본 개념:
    • 컨테이너 객체의 요소들을 순회할 수 있게 해주는 객체
    • for 루프와 같은 반복 구문에서 사용됨
  2. 이터레이터 프로토콜:
    • __iter__() 메서드: 이터레이터 객체를 반환
    • __next__() 메서드: 다음 요소를 반환, 요소가 없으면 StopIteration 예외 발생
  3. iter() 함수:
    • 객체의 __iter__() 메서드를 호출하여 이터레이터 객체를 얻음
  4. next() 함수:
    • 이터레이터의 __next__() 메서드를 호출하여 다음 요소를 얻음
  5. 이터레이터 구현 예시:
class Reverse:
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index -= 1
        return self.data[self.index]
  1. 이터레이터 사용 예:
rev = Reverse('spam')
for char in rev:
    print(char)
# 출력: m, a, p, s
  • 이터레이터를 사용하면 대용량 데이터를 효율적으로 처리할 수 있습니다.
  • 커스텀 이터레이터를 만들어 복잡한 순회 로직을 간단하게 표현할 수 있습니다.
  • for 루프는 내부적으로 이터레이터를 사용합니다.
  • 이터레이터는 한 번만 순회할 수 있습니다. 재사용하려면 새로운 이터레이터 객체를 생성해야 합니다.

제너레이터와 제너레이터 표현식

  1. 제너레이터 (Generators)
    • 정의: 이터레이터를 생성하는 간단하고 강력한 도구
    • 특징:
      • 일반 함수처럼 작성되지만 yield 키워드 사용
      • 호출 간 상태 유지
      • 메모리 효율적 (값을 필요할 때만 생성)
def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

for char in reverse('golf'):
    print(char)
# 출력: f, l, o, g
  1. 제너레이터의 장점:
    • __iter__()__next__() 메서드 자동 생성
    • 지역 변수와 실행 상태 자동 보존
    • 종료 시 자동으로 StopIteration 발생
  2. 제너레이터 표현식 (Generator Expressions)
    • 정의: 간단한 제너레이터를 한 줄로 표현하는 방법
    • 문법: 리스트 컴프리헨션과 유사하지만 대괄호 [] 대신 괄호 () 사용
sum(i*i for i in range(10))  # 0부터 9까지 제곱의 합

data = 'golf'
list(data[i] for i in range(len(data)-1, -1, -1))  # 문자열 뒤집기
  1. 제너레이터 표현식의 특징:

    • 메모리 효율적 (큰 데이터셋 처리에 유용)
    • 즉시 사용되는 상황에 적합
    • 리스트 컴프리헨션보다 간결하지만 융통성은 떨어짐
  2. 팁:

  • 대량의 데이터를 다룰 때 제너레이터를 사용하면 메모리 사용을 최적화할 수 있습니다.
  • 제너레이터는 한 번만 순회할 수 있습니다. 재사용이 필요하면 함수를 다시 호출해야 합니다.
  • 간단한 이터레이션이 필요할 때는 제너레이터 표현식을 사용하면 코드를 간결하게 만들 수 있습니다.
  • 복잡한 로직이 필요한 경우 일반 제너레이터 함수를 사용하는 것이 좋습니다.
728x90