CS 지식

[Python] Mangling / Args인자 / Decorator 에 대해

도리컴 2024. 1. 25. 02:15
반응형

목표

  • mangling, 가변인자, decorator의 정의와 왜 필요한 지를 알고, 어디에 어떻게 쓰는지를 알아보자!
  • python 코드를 보다 위 3가지가 나왔을 때, 당황하지 않고 읽어 넘기기

목차

  • 1.mangling
  • 2.가변인자, 키워드 가변인자
  • 3.decorator

1. Mangling

정의

  • 뭉개다 -> 특정 변수/함수를 뭉갬 -> 외부 코드에서 발견할 수 없도록 하기 위함
  • 클래스 내부의 변수/함수명 앞에 언더스코어(_)를 두 개 붙여 적용
  • 맹글링된 변수/함수는 본연의 이름으로 접근 불가(_클래스명__속성명 으로 접근 가능)
  • 외부의 접근을 조금 어렵게 할 뿐, 완벽히 private이 되는 건 아님

필요한 이유

  • 외부에서의 접근을 어느정도 막기 위해
  • 오버라이딩을 차단하기 위해(종종 하위클래스가 상위클래스의 속성을 오버라이딩 하려는 경우를 방지하려는 목적도 있음)
In [ ]:
#사용 예(외부에서의 접근 차단)
class TestClass:
  def __init__(self): self.name = "왕춘삼"
  self.age = 30
  self.__hobby = "인형놀이" #hobby를 숨김 - private의 특성을 어느정도 띄게 됨 / _클래스명__속성명 으로 접근 가능
  man = TestClass() print(man.name, man.age, man.__hobby) #에러 : hobby를 못찾음
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-29-4b6ad34ef023> in <cell line: 10>()
      8 
      9 man = TestClass()
---> 10 print(man.name, man.age, man.__hobby) #- 에러 : hobby를 못찾음

AttributeError: 'TestClass' object has no attribute '__hobby'
In [ ]:
print(dir(man)) # man의 모든 속성을 출력(dir함수 : 객체의 모든 속성 리스트 반환)
['_TestClass__hobby', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name']
In [ ]:
man._TestClass__hobby #이렇게 접근할 수 있음 / 형식 : _클래스명__속성명
Out[ ]:
'인형놀이'
In [ ]:
#사용 예 2(의도치 않은 오버라이딩 방지) - 자식이 의도치않게 부모의 속성을 오버라이딩 하는 것 방지
class Parent: def __init__(self):
  self._protected_attribute = 10
 
class Child(Parent):
  def __init__(self):
    super().__init__()
    self._protected_attribute = 20 # 상위 클래스의 속성을 오버라이딩(서로 이름이 같아버려서)
 
obj = Child()
 
print(obj._protected_attribute) # - 난 부모껄 참조하고 싶은뎁...
20
In [ ]:
class Parent:
  def __init__(self):
  self.__protected_attribute = 10
 
class Child(Parent):
  def __init__(self):
    super().__init__()
    self.__protected_attribute = 20 # 상위 클래스의 속성을 오버라이딩(서로 이름이 같아버려서)
 
obj = Child()
 
print(obj._Parent__protected_attribute) # - 부모꺼 참조 가능
10

2. 가변인자, 키워드 가변인자

정의

  • ""(가변인자), "*"(키워드 가변인자) - 함수에 유동적으로 파라미터를 전달할 때 사용
  • 일반적으로 args, kwargs라고 부름
  • 가변인자는 튜플 / 키워드 가변인자는 '키워드=값'(딕셔너리) 형태로 값 전달
  • args가 kwargs보다 항상 먼저 -> 함수호출의 가독성과 명확성을 높이기 위한 파이썬의 문법 규칙

필요한 이유

  • 함수 호출 시 인자의 개수를 동적으로 조절할 수 있어 편리하고, 코드의 유연성과 확장성을 높여줌
  • 대표적인 예 : print문

주의

  • 가변인자 남발 시 코드의 가독성이 오히려 떨어지고 함수의 동작 파악이 어려울 수 있음
 
In [ ]:
#가변인자 : *
def print_numbers(*args):
  print(args)
  for arg in args:
    print(arg)
 
#파라미터의 수가 유동적 - 너무 꿀
print_numbers(10)
print_numbers(1, 2, 3, 4)
(10,)
10
(1, 2, 3, 4)
1
2
3
4
  • 가변 인자 : 튜플
  • 키워드 가변인자 : '키워드=값'(딕셔너리)
In [2]:
#키워드 가변인자 : **
 
def personal_info(age, name, address):
 print('이름: ', name)
  print('나이: ', age)
  print('주소: ', address)
 
x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
 
personal_info(**x) #정석은 이렇게 - 각 value값들이 key값에 맞게끔 전달됨
이름:  홍길동
나이:  30
주소:  서울시 용산구 이촌동

3. Decorator

사전지식 : 클로저(closure)

  • 외부 함수의 지역 변수를 참조하는 내부 함수를 클로저 함수라고 함.
  • 내부 함수는 자신이 생성될 때의 환경, 즉 상위 함수의 지역 변수를 '기억'하고 있는 상태가 됨
  • 내부 함수가 호출될 때마다, 내부 함수는 외부 함수의 지역 변수를 '참조'하여 동작을 수행
  • 참고 : https://sungmin93.tistory.com/32



In [ ]:
#print1과 print2함수는 각각 자기가 어떤 값을 출력해야 하는지를 기억하고 있다.
def outer(x):
  def inner():
    print(x)
  return inner
 
print1 = outer(1)
print2 = outer(2)
 
print(type(print1))
print1()
print2()
 
#출처: https://engineer-mole.tistory.com/181 [매일 꾸준히, 더 깊이:티스토리]
<class 'function'>
1
2

Decorator 정의

  • 소스코드를 변경하지 않고 다른 함수/메서드의 동작을 수정할 수 있는 고차함수
  • 적용할 함수 바로 위에 '@데코레이터_이름'을 붙여준다.
  • 모든 Callable Object가 데코레이터가 될 수 있음(함수, 메서드, 변수 모두 가능)

필요한 이유

  • 기존의 코드를 수정하지 않고도 여러가지 기능을 추가할 수 있음
  • DRY(Don't Repeat Yourself) - 깔끔한 코드를 만들며 코드의 반복 줄임 -> 효율 굳
  • 외부에서 특정 함수나 클래스에 개입하는 경우 사용

주의

  • 함수의 서명(반환값, 매개변수, 함수 동작)을 바꿀 수 있기 때문에 신중한 사용 필요
  • 서명이 변경되면 기존에 해당 함수를 사용하던 코드들이 동작하지 않을 수 있음
In [ ]:
#맛보기
def decorator_function(func):
  def wrapper(*args, **kwargs):
    print("이게 먼데")
  return wrapper
 
class Mainclass:
 
  @decorator_function # main_function은 decorator_function(main_function)으로 대체됨 -> wrapper함수로 대체
  def main_function(self):
    print("하이")
 
  def main_function2(self):
    print("하이하이")
 
print("MAIN FUNCTION START")
my = Mainclass()
my.main_function()
my.main_function2()
MAIN FUNCTION START
이게 먼데
하이하이
  • "@decorator_function"이 위에 있으면, 그 함수는 '데코레이터 함수의 반환값'으로 대체
  • 원래 함수와 파라미터는 데코레이터 함수로 넘겨짐


  • 강의에서 나온 예제
In [ ]:
# 메시지 출력 위아래로 별찍기 - 꾸미기
def star(func):
  def inner(*args):
    print(args[1] * 30)
    func(*args)
    print(args[1] * 30)
  return inner
 
@star # printer = star(printer) 이후 printer 실행
def printer(msg, mark):
   print(msg)
   
printer("Hello", "*")
******************************
Hello
******************************
In [ ]:
#2의 49제곱
def generate_power(exponent): # exponent 값은 2 -> @generate_power(2) 이므로
 
  def wrapper(f): # f는 raise_two함수
 
    def inner(*args): # args는 raise_two의 파라미터인 7 -> 형태가 (7,)임
                               # unpacking을 위해 *을 붙여 *args
      result = f(*args) # 7을 넣은 raise_two 결과값이 result에 저장  
      return exponent**result # 2^49 return
    return inner
  return wrapper
 
# @~(데코레이터)가 붙는 순간, raise_two호출마다
# generate_power 함수를 호출하고, 파라미터로 raise_two함수가 들어감
 
@generate_power(2) # raise_two = generate_power(2)(raise_two) 를 실질적으로 수행
def raise_two(n):
  return n**2
 
print("2^49 = ", raise_two(7))
2**49
2^49 =  562949953421312
Out[ ]:
562949953421312
In [ ]:
# 함수를 추적하는 기능을 추가할 때 -> decorator 사용 예
def trace(func): # 호출할 함수를 매개변수로 받음
  def wrapper():
    print(func.__name__, '함수 시작') # __name__으로 함수 이름 출력 func() # 매개변수로 받은 함수를 호출     
    func()
    print(func.__name__, '함수 끝') return wrapper # wrapper 함수 반환
 
@trace # 데코레이터를 사용해 hello에 추적 기능 추가
def hello():
  print('hello')
 
@trace # 데코레이터를 사용해 world에 추적 기능 추가
def world():
  print('world'
 
hello() # 함수를 그대로 호출
world() # 함수를 그대로 호출
 
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝
In [1]:
#데코레이터를 이용한 타이머 함수 -> 해당 함수의 실행시간을 기록하려고 함!
import time
 
def timer_decorator(func):
  def wrapper(*args, **kwargs):
    start_time = time.time() # 시작 시간 측정
    result = func(*args, **kwargs) # 원래 함수 실행
    end_time = time.time() # 종료 시간 측정
    print(f"{func.__name__} 함수의 실행 시간: {end_time - start_time}초")
    return result
  return wrapper
 
@timer_decorator # 데코레이터를 사용해 my_function에 타이머 기능 추가
def my_function(n):
  total = 0
  for i in range(n):
     total += i
  return total
 
result = my_function(1000000) # 함수 실행 및 실행 시간 출력
print(result)
# 출력
#my_function 함수의 실행 시간: 0.12572193145751953초
my_function 함수의 실행 시간: 0.09530282020568848초
499999500000

결론

  • Mangling은 변수/함수를 뭉개서 다른이름으로 사용하게끔 하는 기법이다!
  • 가변인자, 키워드 가변인자는 유동적인 개수의 값을 입력받을 수 있는 파라미터이다!
  • 데코레이터는 원본함수의 수정 없이 특정 함수/변수/클래스의 부가적인 기능을 구현할 때 사용된다!
  • 이해가 완벽히 되지 않았더라도, '줄건 준다는 마인드'를 장착하고 필요할 때 다시 찾아보기로 한다!
반응형