학습 정리 - 정보 은닉, 클래스 변수, 클래스 함수, static 함수 (2025.01.20)

수아·2025년 1월 20일
0

학습 정리

목록 보기
15/51
post-thumbnail

회고 리스트

1. 정보은닉이란?

정보 은닉(Information Hiding)은 객체지향 프로그래밍(OOP)의 중요한 개념 중 하나로 클래스 내부의 데이터(속성)와 구현 세부 사항을 외부에서 직접 접근하지 못하도록 숨기고 필요한 경우에만 제한된 방식으로 접근을 허용하는 것이다.

파이썬에서는 주로 변수 앞에 언더바( _ )를 사용해서 접근 수준을 표시한다.

1) public(공개)
:일반적으로 주로 사용하는 변수나 메서드처럼 이름 앞에 아무것도 붙이지 않은 경우
: 외부에서 자유롭게 접근 가능하다.

2) Protected (보호)
: 변수나 메서드 이름 앞에 단일 언더스코어( _ )를 붙이는 경우
: 외부에서 직접 접근은 가능하지만 내부적으로만 사용하라는 암묵적인 규칙이 있다.

3) Private (비공개)
: 변수나 메서드 이름 앞에 이중 언더스코어( __ )를 붙이는 경우
: Name Mangling(이름 변환)으로 외부에서 접근을 어렵게 만든다.

  • 안정성 : 데이터를 보호하고 무결성을 유지한다.
  • 유지보수성 : 내부 구현을 바꾸더라도 외부에 영향을 주지 않는다.
  • 코드 가독성 : 변수의 역할과 사용 의도를 명확히 표현한다.

2. 아래의 소스코드에서 에러가 나는 이유와 age 50으로 바꾸어 보시오.

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    # if 문의 해당 함수에서 0이 들어 가지 않도록 막음
    def add_age(self,age):
        if age < 0:
            print('나이는 0보다 커야 합니다. 나이 정보 오류')
        else:
            self.__age += age

    def __str__(self):
        return f'이름은 {self.__name}, 나이는 {self.__age}'


p = Person('홍길동', 20)
p.__age = 30  

__age는 Private 변수로 Name Mangling에 의해 _Person__age로 변환되어 외부에서 직접 접근할 수 없다.


1) Setter 메서드를 사용하는 방법

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    # 나이를 설정하는 메서드
    def set_age(self, age):
        if age < 0:
            print("나이는 0보다 커야 합니다.")
        else:
            self.__age = age

    def add_age(self, age):
        if age < 0:
            print('나이는 0보다 커야 합니다. 나이 정보 오류')
        else:
            self.__age += age

    def __str__(self):
        return f'이름은 {self.__name}, 나이는 {self.__age}'


p = Person('홍길동', 20)
p.set_age(50)  	# 나이를 직접 설정
print(p)       	# 출력: 이름은 홍길동, 나이는 50

2) @classmethod 로 변경하는 방법

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    @classmethod
    def set_person_age(cls, person_instance, age):
        if age < 0:
            print("나이는 0보다 커야 합니다.")
        else:
            person_instance.__age = age

    def __str__(self):
        return f'이름은 {self.__name}, 나이는 {self.__age}'


p = Person('홍길동', 20)
Person.set_person_age(p, 50)	# 클래스 메서드를 통해 나이 변경
print(p)  							# 출력: 이름은 홍길동, 나이는 50

3) __dict__ 직접 접근 방식

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    # if 문의 해당 함수에서 0이 들어 가지 않도록 막음
    def add_age(self,age):
        if age < 0:
            print('나이는 0보다 커야 합니다. 나이 정보 오류')
        else:
            self.__age += age

    def __str__(self):
        return f'이름은 {self.__name}, 나이는 {self.__age}'


p = Person('홍길동', 20)
# p.__age = 30                # 이중 언더스코어를 사용

p.__dict__['_Person__age'] = 50
print(p.__dict__)           # 출력 : {'_Person__name': '홍길동', '_Person__age': 50}

3. 데코레이터란?

데코레이터(Decorator)는 코드의 기능을 확장하고 반복적인 작업을 제거하며 코드 가독성을 높인다.
다른 함수를 감싸는 함수 또는 클래스이고 함수를 인자로 받아 새로운 함수를 반환한다.
'@데코레이터_이름' 형태이고 특정 함수나 클래스에 추가적인 동작을 적용할 수 있다.

  • 중복 코드 제거 : 여러 함수에서 반복적으로 사용되는 동작을 데코레이터로 추출한다.
  • 분리된 책임 : 함수의 핵심 로직과 부가적인 동작을 분리한다.
  • 가독성 증가 : 함수의 동작에 추가된 기능을 쉽게 확인할 수 있다.

4. 아래와 같이 출력이 나오도독 데코레이션 함수를 만들어 보시오.

def smile():
    print('^_^')

def confused():
    print('@_@')

def deco(fun):

    def wrapper():
        print('emotion!')
        fun()
        print('emotion!')

    return wrapper

================
# emotion!
# ^_^
# emotion!

1) @deco를 사용한 데코레이터

def deco(fun):

    def wrapper():
        print('emotion!')
        fun()
        print('emotion!')

    return wrapper

@deco
def smile():
    print('^_^')

smile()

2) 직접 함수 호출

def smile():
    print('^_^')

def confused():
    print('@_@')

def deco(fun):

    def wrapper():
        print('emotion!')
        fun()
        print('emotion!')

    return wrapper


smile_deco = deco(smile)
smile_deco()

5. 아래와 같이 출력이 나오도록 adder_deco 를 만들어 보시오.

@adder_deco
def adder1(n1,n2):
    return n1 + n2

@adder_deco
def adder2(n1,n2 ,n3):
    return n1 + n2 + n3

@adder_deco
def adder3(n1,n2 ,n3,n4):
    return n1 + n2 + n3 + n4
======
8
6
10

adder1(3,5) # 8
adder2(1,2,3) # 6
adder3(1,2,3,4) # 6

def adder_deco(func) :      # 함수를 인자로 받는다.
    
    def add(*n) : 
        return func(*n)
        
    return add

6. 아래를 예를 들어 설명하시오.

  • 클래스 변수
    : 클래스 내부 (클래스 메서드 외부)에 선언되고 클래스와 모든 인스턴스에서 공유되는 변수
class Example:
    class_var = "클래스 변수"  # 클래스 변수

    def __init__(self, instance_var):
        self.instance_var = instance_var  # 인스턴스 변수

# 클래스 변수 접근
print(Example.class_var)		# 출력: 클래스 변수

# 인스턴스 생성 후 클래스 변수와 인스턴스 변수 접근
obj = Example("인스턴스 변수")
print(obj.class_var)  # 출력: 클래스 변수
print(obj.instance_var)			# 출력: 인스턴스 변수

# 클래스 변수 변경
Example.class_var = "변경된 클래스 변수"
print(obj.class_var)			# 출력: 변경된 클래스 변수
  • 클래스 함수
    : '@classmethod' 데코레이터를 사용해 정의한다.
    : 첫 번째 매개변수로 cls를 받아 클래스에 속한 데이터에 접근하거나 조작할 수 있다.
class Example:
    class_var = "클래스 변수"

    @classmethod
    def class_method(cls):
        return f"클래스 변수: {cls.class_var}"

# 클래스 함수 호출
print(Example.class_method())		# 출력: 클래스 변수: 클래스 변수

# 인스턴스를 통해서도 호출 가능
obj = Example()
print(obj.class_method())			# 출력: 클래스 변수: 클래스 변수
  • 스태틱 함수
    : 정적 메서드로 '@staticmethod' 데코레이터를 사용해 정의한다.
    : 클래스나 인스턴스와 관계없는 독립적인 작업을 수행한다.
class Example:
    @staticmethod
    def static_method(x, y):
        return x + y

# 클래스 이름으로 호출
print(Example.static_method(3, 5))		# 출력: 8

# 인스턴스를 통해 호출
obj = Example()
print(obj.static_method(10, 20))		# 출력: 30

7. 아래가 에러가 나는 이유를 설명하시오.

class Calculator:

   @staticmethod # 스태틱 메소드 = 정적 메소드 = 간단한 함수들 = 객체와 상관없는 함수들
   def add(n1, n2):
       return n1 + n2

   def add2(self, n1,n2): #인스턴스 메소드
       return n1 + n2

   @staticmethod
   def mul(n1, n2):
       return n1 * n2
       

cal = Calculator()
print(Calculator.add2(10,20)) 
  • 에러 원인
    : add2는 인스턴스 메서드첫 번째 매개변수로 인스턴스(self)를 필요로 한다.
    클래스 이름으로 호출하면 self를 전달하지 않아 TypeError가 발생한다.
    self에 해당하는 인스턴스 객체를 명시적으로 전달해야 한다.
    따라서 Calculator.add2(10, 20) 대신 아래 두가지 방식 중 하나로 호출해야 한다.
  • 인스턴스를 통해 호출
    : cal.add2(10, 20)

  • 명시적으로 인스턴스를 전달
    : Calculator.add2(cal, 10, 20)

8. 아래의 출력 결과를 예측하시오.

def decorator1(func):
    def wrapper():
        print('decorator1')
        func()
    return wrapper
 
def decorator2(func):
    def wrapper():
        print('decorator2')
        func()
    return wrapper
 
데코레이터를 여러 개 지정
@decorator1
@decorator2
def hello():
    print('hello')
 
hello()

hello 함수를 decorator1과 decorator2로 감쌌기 때문에
decorator1 / decorator2 / hello
이렇게 출력될 것이다.

9. 아래가 에러가 나는 이유와 수정을 하시오.

class Cirle2:
    PI = 3.12419
    
    def get_area(cls, radius):
        return cls.PI * radius * radius
=============================
print(Cirle2.PI)

result = Cirle2.get_area(5) 
print(result)

10. 프로퍼티 함수에 대하여 설명하시오.(예습)

프로퍼티 함수(Property Function)는 클래스 속성에 접근하는 방식을 더 안전하고 직관적으로 만들기 위한 기능이다.
Getter/Setter를 연결해 속성처럼 접근하면서 내부적으로는 메서드를 실행한다.

'property( fget, fset, fdel, doc )' 형태로 사용한다.

  • fget: 값을 가져오는 getter 메서드
  • fset: 값을 설정하는 setter 메서드
  • fdel: 속성을 삭제하는 deleter 메서드
  • doc: 프로퍼티의 문서화 문자열
class Person:
    def __init__(self, name, age):
        self.__name = name  # 비공개 변수
        self.__age = age

    def get_age(self):  # Getter
        return self.__age

    def set_age(self, age):  # Setter
        if age < 0:
            print("나이는 0 이상이어야 합니다.")
        else:
            self.__age = age

    age = property(get_age, set_age)  # Getter와 Setter 연결

p = Person("Alice", 25)
print(p.age)  # Getter 호출: 25
p.age = 30    # Setter 호출
print(p.age)  # Getter 호출: 30
p.age = -5    # Setter 호출: "나이는 0 이상이어야 합니다."

뭔가 예제만 보면 setter와 getter 중 (property 괄호 안에 있는 것들 중) 제일 맞는 거 같은 거 하나씩 뽑아서 쓸 수 있는 느낌인데 맞는지 모르겠다.

print문 안에 있으니까 setter는 아니겠지? -> getter로 씀
값을 변경하려는 거 같네? -> getter는 아닌듯 -> setter로 씀

이런 느낌이다.


3줄 요약:
1.정보은닉 이란 변수를 최대한 노출 시키지 않도록 하는것이다.
2.파이썬에서 객체의 변수와 값은는 __dict__ 딕셔너리 객체로 따로 관리 한다.
3. __변수 이름은 __dict__['_클래스명__변수이름'] 으로 접근 가능하다.

3줄요약 두번째:
4. 데코레이터는 함수호출이며 함수의 파라미터로 콜백함수를 넘긴다.
5. 클래스 변수는 공용변수 이며, 각 객체들이 공유 하는 변수이다.
6. 프로퍼티는 쪼매 어렵다.


궁금했던 점 (해결)

1 -1) 함수를 먼저 정의한 뒤 @deco~~ 로 지정하면 안될까?

@deco~~할 때 선언해야만 한다.
함수 정의 시점에만 동작하기 때문에 미리 정의했던 함수를 가져오면 안된다.

def deco(fun):
    def wrapper():
        print("emotion!")
        fun()
        print("emotion!")
    return wrapper

def smile():
    print("^_^")

# 여기서 함수 호출
smile()  	# ^_^ 출력 (데코레이터 적용 안 됨)

# 나중에 데코레이터 작성
@deco
smile()		# SyntaxError: invalid syntax

1 - 2) 근데 4-2번을 보면 @deco~~ 사용하지 않았다.

: @deco~~를 사용하지 않고 deco(smile)로 직접 감쌌기 때문이다.

4-1번의 경우 편리하지만 smile이 .wrapper로 덮어져서 원본함수로 접근하기가 어렵다.
4-2번의 경우 원본 smile 함수는 유지되고 wrapper의 결과는 smile_deco를 통해 접근 가능하다.


2) 여러 데코함수를 사용할 때 @deco~~ 한 번만 써도 될까?

함수 하나마다 각각 데코레이터를 붙여야 한다.


3) 근데 데코 함수를 기능 추가 목적으로 쓰는거면 그냥 클래스 써서 상속하면 안 됨?

상속과 데코레이터는 비슷하지만 목적과 사용사례가 다르다.

  • 상속
    : 클래스 구조를 확장하거나 공통 동작을 가진 여러 클래스에서 코드 중복을 제거할 때 적합하다.
    : 부모 클래스의 모든 기능을 재사용 가능하지만 클래스 간 강한 결합이 발생하고 상속의 깊이가 깊어지면 유지 보수가 어려워진다.

  • 데코레이터
    : 기능을 추가하거나 수정하고 싶지만 기존 함수나 클래스의 구조를 변경하지 않으려는 경우에 적합하다.
    : 재사용성이 높지만 함수에 한정된 동작을 추가하는데 적합하고 클래스 구조 확장이 어렵다.


4) 클래스 함수랑 인스턴스 함수는 같은 거 아님?

: 둘 다 클래스 내부에 정의된 메서드지만 사용하는 방식, 목적, 접근 범위가 다르다.

  • 클래스 함수 (@classmethod)
    : '@classmethod' 데코레이터로 정의되며 첫 번째 매개변수로 클래스 자체를 나타내는 cls를 받는다.
    : 인스턴스 상태(인스턴스 변수와 그 값들)에 접근할 수 없습니다.
    : 클래스 이름 또는 인스턴스를 통해 호출 가능

  • 인스턴스 함수
    : 인스턴스 메서드는 일반적으로 정의되며 첫 번째 매개변수로 인스턴스 자신을 나타내는 self를 받는다.
    : 인스턴스 변수에 접근하거나 조작할 수 있다.
    : 반드시 인스턴스를 통해 호출해야 하며 클래스 이름으로 직접 호출하면 self를 전달하지 않아 오류가 발생한다.


사실 아직 클래스 함수 / 스태틱 함수 이런 걸로 물어보면 헷갈리긴 한다....
별찍기 할 때가 그립다...

0개의 댓글