[플레이데이터] 4월 24일 오전 수업 -2(데코레이터, 프로퍼티, 언더스코어, 정적 메서드, 클래스 상속)

싱숭생숭어·2023년 4월 25일
1

플레이데이터 수업

목록 보기
5/21

데코레이터

  • 데코레이터란 장식한다는 의미. 어떤 함수가 있을 때 해당 함수를 직접 수정하지 않고 함수에 기능을 추가하고자 할 때 데코레이터를 사용함.

데코레이터 만들기

def hello():
	print("hello") #기존 함수 선언

def deco(fn):
    def deco_hello():
        print("*" * 20)    # 기능 추가
        fn()               # 기존 함수 호출
        print("*" * 20)    # 기능 추가
    return deco_hello
    
deco_hello = deco(hello) # 데코레이터에 호출할 함수를 넣음
hello() # 반환된 함수 호출 

>>>********************
hello
********************

  • 기존 함수를 입력 받아서 기능이 추가된 새로운 함수 객체로 만들어주는 역할 = 데코레이터

@기호 사용하기

  • 위의 예제와 같이 이미 선언된 데코레이터 함수(deco)가 있을 경우, 기능을 추가하고자 하는 해당 함수 위에 @데코레이터함수와 같이 적어주는 방법으로 데코레이터를 사용하는 것도 가능함.
@deco
def hello():
	print("hello")

hello()

>>>********************
hello
********************

프로퍼티

  • 클래스에서 메서드를 통해 속성의 값을 가져오거나 저장하는 경우가 존재함. 이때 값을 가져오는 메서드를 getter, 값을 저장하는 메서드를 setter라고 부름.
class Person:
    def __init__(self):
        self.__age = 0
 
    def get_age(self):           # getter
        return self.__age
    
    def set_age(self, value):    # setter
        self.__age = value
 
james = Person()
james.set_age(20)
print(james.get_age())

>>>20
  • 파이썬에서는 @property를 사용하면 getter, setter를 더 간단하게 구현 가능
class Person:
    def __init__(self):
        self.__age = 0
 
    @property
    def age(self):           # getter
        return self.__age
 
    @age.setter
    def age(self, value):    # setter
        self.__age = value
 
james = Person()
james.age = 20      # 인스턴스.속성 형식으로 접근하여 값 저장
print(james.age)    # 인스턴스.속성 형식으로 값을 가져옴

>>>20
  • getter, setter 메서드의 이름은 둘다 동일하게 age

  • getter에는 데코레이터로 @property를, setter에는 데코레이터로 @age.setter가 붙음. 즉, 값을 가져오는 메서드에는 @property 데코레이터를 붙이고, 값을 저장하는 메서드에는 @메서드이름.setter 데코레이터를 붙이는 방식임

  • 데코레이터를 활용하는 위 방식의 경우 james.age 처럼 메서드를 속성처럼 사용 가능(()를 붙이지 않고도 호출이 가능)

  • 값을 저장할 때는 james.age = 20처럼 메서드에 바로 할당하면 되고, 값을 가져올 때도 james.age처럼 메서드에 바로 접근 가능

프로퍼티 예제

class Book:
    def __init__(self, raw_price):
        if raw_price < 0 :
            raise ValueError('price must be positive')
        self.raw_price = raw_price
        self._discounts = 0
        
    @property
    def discounts(self):
        return self._discounts
    
    @discounts.setter
    def discounts(self, value):
        if value < 0 or 100 < value: 
            raise ValueError('discounts must be between 0 and 100')
        self._discounts = value 
        
    @property
    def price(self):
        multi = 100 - self._discounts 
        return int(self.raw_price * multi / 100)
book = Book(2000)
book.discounts

>>>0
book.price

>>>2000
book.discounts = 20
book.price
>>>1600
book.price = 100
>>>AttributeError: can't set attribute
  • price.setter가 붙은 인스턴스 메서드가 정의되어 있지 않아 오류가 발생

언더바/ 언더스코어/ 밑줄문자(_)

언더스코어(_)의 사용 용도로 4가지가 있음
1. 인터프리터에서 사용
2. 무시하는 값
3. 반복문에서 사용
3. 숫자값의 분리(자릿수 구분, 구분자)
4. 변수명에서 사용

1. 인터프리터에서의 사용

  • 파이썬 인터프리터에서 가장 마지막 표현식의 결과값을 자동으로 _라는 변수에 저장

  • 여기에 저장된 값을 다른 변수에 저장 가능

  • 일반적인 값처럼 사용 가능

>>> 5 + 4 
9 
>>> _
9
>>> _ + 6
15
>>> a = _
>>> a
15

2. 무시하는 값

  • unpack할 때 특정 값을 사용하지 않으려면 해당 값을 밑줄_에 지정
# 하나의 값 무시
a, _, b = (1, 2, 3) 
print(a, b) # 1,3

# 여러 개의 값 무시
# *을 이용하여 여러 개의 값을 하나의 변수에 저장할 때 쓰임
# "Extended Unpacking"이며, Python 3.x에서 사용 가능
a, *_, b = (7, 6, 5, 4, 3, 2, 1)
print(a, b) # 7,1

3. 반복문에서 사용

  • 일반 변수처럼 사용 가능
a = []
for _ in range(5):
   a.append(_)
a

>>>[0, 1, 2, 3, 4]
 _ = 5
 while _ < 10:
   print(_, end = ' ')
   _ += 1
 # end의 기본값은 \n 이나, 띄어쓰기로 바꿔줌
 
>>> 5 6 7 8 9

4. 숫자값의 구분

  • 숫자가 길다면 자릿수 구분을 위해 _를 사용

  • 2진수, 8진수, 16진수 사용 가능

  • 파이썬은 기본 10진수 -> 나머지 진수의 경우 _앞에 접두어가 붙음

million = 1_000_000
binary = 0b_0010 # 2진수
octa = 0o_64 # 8진수
hexa = 0x_23_ab # 16진수

print(million) # 1000000
print(binary) # 2
print(octa) # 52
print(hexa) # 9131

5. 변수명에서 사용

  • 변수, 함수, 클래스 명 등에 언더바 사용

5-1 앞에 하나의 언더스코어: _variable

  • 내부용으로만 사용
class Test:
	def__init__(self):
    	self.name = "test"
        self._num = 7
 obj = Test()
 print(obj.name) # test
 print(obj._num) # 7
  • 모듈을 import해서 사용할 때
# my_functions.py
def func():
	return "test"
def _private_func():
	return 7
    
# 다른 파일에서 my_function.py를 import
# 하지만 파이썬은 언더바 하나로 시작한 이름들은 import하지 않는다!
>>> from my_functions import *
>>> func()
"test"
>>> _private_func()
Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name '_private_func' is not defined


# 모듈 자체를 import하기!
>>> import my_functions
>>> my_functions.func()
"test"
>>> my_functions._private_func()
7

5-2 뒤에 한개의 언더스코어: variable_

  • 파이썬 키워드(예약어)를 변수, 함수, 클래스 명 등으로 사용하려는 경우

  • 이름 끝에 _를 사용하여 파이썬 키워드와의 충돌 방지 가능

a = [1,2,3,4]
sum = sum(a)
print(sum)

b = [3,4,5]
sum = sum(b)
print(sum)
  • sum은 리스트안의 값들을 모두 더하는 파이썬의 예약어

  • 위 a 구문에선 sum을 변수로, b 구문에선 sum의 예약어 기능을 사용하기에 오류 발생 'int' object is not callable

  • 이런 오류를 피하기 위해 sum 대신 sum_을 사용

a = [1,2,3,4]
sum_ = sum(a)
print(sum_)

b = [3,4,5]
sum_ = sum(b)
print(sum_)

5-3 앞에 두개의 언더스코어: __variable

  • name mangling: 파이썬이 해당 변수/함수의 이름을 짓이겨 바꿔버린다는 의미

  • 맹글링을 당한 변수/함수는 원래의 이름을 접근할 수 없음

  • 언더바 2개로 맹글링을 적용한 변수/함수는 _클래스명__변수/함수명으로 이름이 변경됨

class TestClass():
    def __init__(self):
        self.name = "Peter"
        self.gender = "male"
        self.__age = 32

man = TestClass()

print(man.name) # Peter
print(man.gender) # male
print(man.__age) # AttributeError 발생

# dir함수는 클래스 객체의 모든 attribute들을 리턴
# gender, name는 보이지만 __age는 _TestClass__age라는 이름이 보인다
print(dir(man))
['_TestClass__age', '__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__', 'gender', 'name']
  • dir 함수를 통해 보면 man안에 함수와 변수에 __age가 없는 걸 확인 가능

  • 언더스코어 두개로 시작하는 것은 private access modifier(접근 지정자)의 역할을 함. 이는 사용자가 만든 클래스의 변수를 변환하는데, 서브 클래스에서의 이름이 충돌나는 것을 방지하기 위해 사용함. 변수 뿐 아니라 메소드에서도 사용 가능

print(man._TestClass__age)

or 

class TestClass():
    def __init__(self):
        self.name = "Peter"
        self.gender = "male"
        self.__age = 3
        
    def get_age(self):
    	return self.__age

print(man.get_age())
  • 위 두가지 방식을 활용하여 맹글링된 변수에 접근할 수 있음
class TestClass():
    def __init__(self):
        self.name = "Peter"
        self.gender = "male"
        self.__list = [1]
        
    def get_list(self):
    	return self.__list
     
    @property
    def chk_list(self):
    	return self.__list
        
man = TestClass()
print(man.name)
print(man.get_list()) 
man.chk_list.append(10) #이 구문을 man.get_list.append(10) 또는 man.chk_list().append(10)으로 쓰면 error 발생
print(man.get_list())

#Peter
#[1]
#[1, 10]
  • 맹글링된 변수에 값을 직접 추가하는 방식은 위에서 배운 property 데코레이터를 사용하면 가능

5-4 앞 뒤에 두개의 언더스코어: variable

  • 특수한 예약 함수나 변수, 함수명으로 매직 메소드(magic method) 또는 dunder 메소드라고 함

  • 대표적으로 __init__, __add__, __len__, __main__

  • 클래스 안에 정의가능한 스페셜 메서드이며, 클래스를 int(), str(), list()등 파이썬의 빌트인 타입과 같은 작동을 하게 해줌

  • +, -, >, < 등의 오퍼레이터에 대해 각각의 데이터 타입에 맞는 메소드로 오버로딩해 백그라운드 연산


정적 메서드

  • 둘다 인스턴스를 생성하지 않아도 class 메서드를 바로 실행 가능

1. 클래스 메서드

class 클래스 명:
	@classmethod
    def 메서드 명(cls, 매개변수1, 매개변수2):
    	코드
  • 기본 구조는 위와 같음

  • 클래스 메서드는 클래스에 속한 메서드로, 첫 번째 인수(cls)에 클래스 객체를 전달

  • 클래스 메서드는 @classmethod를 붙이는 점 이외에는 인스턴스 메서드와 동일한 형태로 정의

  • 첫 번째 인수가 클래스 객체이므로 일반적으로 self가 아닌 cls로 기술

클래스 메서드 예제

class Person:
    count = 0    # 클래스 속성
 
    def __init__(self):
        Person.count += 1    # 인스턴스가 만들어질 때
                             # 클래스 속성 count에 1을 더함
 
    @classmethod
    def print_count(cls):
        print('{0}명 생성되었습니다.'.format(cls.count))    # cls로 클래스 속성에 접근
 
james = Person()
maria = Person()
 
Person.print_count()    
# 2명 생성되었습니다.
  • 인스턴스가 생성될 때마다 +1씩 count되므로 __init__ 메서드에서 클래스 속성에 count += 1

  • 첫 번째 매개변수 cls에는 현재 클래스가 반환. 즉 cls.count처럼 cls로 클래스 속성 count에 접근이 가능

  • 클래스 메서드는 인스턴스 없이 호출 가능, 하지만 메서드 안에서 클래스 속성, 클래스 메서드에 접근 시 사용

  • cls 사용 시 메서드 안에서 현재 클래스의 인스턴스 생성 가능

2. 스택틱 메서드

class 클래스 명:
	@staticmethod
    def 메서드 명(매개변수1, 매개변수2):
    	코드
  • 기본 구조는 위와 같음

  • 함수처럼 동작하는 메서드

  • 클래스 메서드와 거의 같은 구문, @staticmethod 사용

  • 인수에는 인스턴스나 클래스 객체는 전달X, 호출 시 전달한 값이 그대로 전달

3. 클래스 메서드와 스택틱 메서드의 차이

#classmethod
class hello:
    num = 10

    @classmethod
    def calc(cls, x):
        return x + 10 + cls.num #cls.num을 통해 hello 클래스의 num 속성에 접근

print(hello.calc(10))
#결과
30

#staticmethod
class hello:
    num = 10

    @staticmethod
    def calc(x):
        return x + 10 + hello.num 

print(hello.calc(10))
#결과
30
  • 같은 의미의 구문을 표현할 수 있음
class hello:
    t = '내가 상속해 줬어'

    @classmethod
    def calc(cls):
        return cls.t

class hello_2(hello):
    t = '나는 상속 받았어'

print(hello_2.calc())
#결과
내가 상속해 줬어
  • 상속관계가 있는 클래스에서 클래스 메서드를 사용한 경우

  • 상속받은 hello_2 클래스가 t 속성을 업데이트

  • 위의 클래스 메서드에서 cls.t가 상속시켜준 클래스에 존재하더라도 상속받은 클래스의 t 속성임을 명시함 -> 따라서 t는 hello 클래스에서 찾아옴


클래스 다중 상속

  • 다중 상속은 여러 기반 클래스로부터 상속을 받아서 파생 클래스를 만드는 방법
class 기반클래스이름1:
    코드
 
class 기반클래스이름2:
    코드
 
class 파생클래스이름(기반클래스이름1, 기반클래스이름2):
    코드
  • 다중 상속의 기본 구조는 위와 같음

다중 상속 예제 1

class Person:
    def greeting(self):
        print('안녕하세요.')
 
class University:
    def manage_credit(self):
        print('학점 관리')
 
class Undergraduate(Person, University):
    def study(self):
        print('공부하기')
 
james = Undergraduate()
james.greeting()         # 안녕하세요.: 기반 클래스 Person의 메서드 호출
james.manage_credit()    # 학점 관리: 기반 클래스 University의 메서드 호출
james.study()            # 공부하기: 파생 클래스 Undergraduate에 추가한 study 메서드

다중 상속 예제 2

class A:
    def greeting(self):
        print('안녕하세요. A입니다.')
 
class B(A):
    def greeting(self):
        print('안녕하세요. B입니다.')
 
class C(A):
    def greeting(self):
        print('안녕하세요. C입니다.')
 
class D(B, C):
    pass
 
x = D()
x.greeting()    # 안녕하세요. B입니다.

  • 이런 클래스 간의 관계를 다이아몬드 상속이라 부름

  • 클래스 A를 상속받아서 B, C를 만들고, 클래스 B와 C를 상속받아서 D를 만듦

  • 여기서 D는 ABC중 어떤 클래스의 메서드를 호출해야하는지는 메서드 탐색 순서로 확인 가능

메서드 탐색 순서 확인하기

  • 파이썬에서는 메서드 탐색 순서(Method Resolution Order, MRO)를 따름
>>> D.mro()
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
  • MRO에 따르면 D의 메서드 호출 순서는 D, B, C, A 순(보통 다중 상속에서 클래스의 목록 중 왼쪽 -> 오른쪽 순이므로 D에서는 B -> C)

  • 따라서 D에는 greeting 메서드가 없으므로 B의 greeting이 호출됨

  • 여기서 나오는 object클래스는 모든 클래스의 조상. 즉, 어떤 클래스든 MRO를 출력하면 object가 출력됨.

Reference

profile
공부합시당

0개의 댓글