HAZEL

[ Python : Class & Method ] 객체 지향 프로그래밍 , 클래스 , __str__(self) , __repr__(self) , 클래스 변수 , 인스턴스 변수, 클래스 메소드, 인스턴스 메소드, 스테이틱 메소드 본문

PROGRAMMING/Python

[ Python : Class & Method ] 객체 지향 프로그래밍 , 클래스 , __str__(self) , __repr__(self) , 클래스 변수 , 인스턴스 변수, 클래스 메소드, 인스턴스 메소드, 스테이틱 메소드

Rmsid01 2021. 5. 23. 17:55

Chapter 01 : 객체 지향 프로그래밍

: 객체 지향 프로그래밍 ( oop ) 는 코드의 재사용 ,코드 중복 방지 등의 장점이 있다. 

 

1. 객체지향적이지 않은,  날것으로 코딩을 하기

# 리스트 구조
student_names_list = ['Kim','Lee','Park']
student_numbers_list = [1,2,3]
student_grades_list = [1,2,4]
student_details_list = [
    {'gender':'Male', 'score1':95 , 'score2' : 88},
    {'gender':'FeMale', 'score1':77 , 'score2' : 88},
    {'gender':'Male', 'score1':95 , 'score2' : 88},
]

# 삭제
del student_names_list[1]
del student_numbers_list[1]
del student_grades_list[1]
del student_details_list[1]

print(student_names_list, student_numbers_list, student_grades_list) # ['Kim', 'Park'] [1, 3] [1, 4]
print(student_details_list)
#[{'gender': 'Male', 'score1': 95, 'score2': 88}, {'gender': 'Male', 'score1': 95, 'score2': 88}]

: 위에처럼 클래스를 만들지 않고, 코드를 짜게 되면 같은 코드를 반복해서 쳐야하는 번거로움이 있다. 

 

이러한 문제를 해결하기 위해서 등장한 것이 '객체지향' 이다

 

2. 객체지향적인 코드 - 클래스 구조

class Student(): # 클래스
    def __init__(self, name, number, grade,details):  # 생성자
        self._name = name
        self._number = number
        self._grade = grade
        self._details = details

    # 우선순위는 str - > repr 이며, 둘다 없으면 그냥 객체를 반환
    def __str__(self):
        return 'str : {}'.format(self._name)

    def __repr__(self):
        return 'repr : {} - {}'.format(self._name , self._number)


student1 = Student( 'Kim' ,1,  1 , {'gender':'Male', 'score1':95, 'score2' : 30})
student2 = Student( 'Lee' ,2,  1 , {'gender':'FeMale', 'score1':95, 'score2' : 30})
student3 = Student( 'Park' ,3,  1 , {'gender':'Male', 'score1':95, 'score2' : 30})


print(student1.__dict__)  
# student1 에 어떤값이 들어갔는지 다 확인할 수 있다. - 이건, 파이썬이 만들어졌을 때부터 이렇게 된거다!

print(student1, student2, student3)  # str : Kim str : Lee str : Park

 

3. __str__(self) , __repr__(self) 

: 이 두 메서드는 객체를 사용자가 이해할 수 있는 문자열로 반환하도록 해주는 함수이다.

 

위의 코드를 보면

print(student1 , student2, student3) 에 대해서, 각 값이 들어있는 메모리의 수치가 아니라, __str__ 함수에 적혀있는 문자열이 반환됨을 확인 할 수 있다. ( ex , str : Kim str : Lee str : Park )

 

만약, __str__ 함수나, __repr__ 함수가 선언되지 않는다면,

print(student1) 을 하면, <__main__.Student object at 0x10a71f5e0>  가 반환된다. 

for x in student_list:
    print(repr(x))  # print 에 repr 이라는 메소드가 있음
    print(x)
#str : Kim  # str   함수가 있기 때문에, print() 해줬을때, lee 가 나오게된다. str 함수가 없다면, 그냥
# <__main__.Student object at 0x10f7635e0> - 가 나오게 된다.
# str : Lee
# str : Park

 

print(x) 를 해주었을 때, str 가 우선순위이며, 그 다음 repr 이다. 

repr 는 print(repr(x)) 를 입력하여 직접 출력해줄 수 도 있다. 

 

4. 클래스 변수와 인스턴스 변수

: 아래의 4, 5, 번에 대한 설명을 쉽게 이해하기 위한 class 코드 

# 클래스 재 선언
class Student():
    """
    Student Class
    Author : LEE
    Data : 2021.05.21
    """

    # 클래스 변수를 선언
    student_count = 0

    def __init__(self, name , number, grade, details , email = None):
        # 인스턴스 변수들
        self._name = name
        self._number = number
        self._grade = grade
        self._details = details
        self._email = email

        # 객체가 선언될때마다 cnt 가 하나씩 증가하게 된다.
        Student.student_count += 1


    # 메소드 들 ..
    def __str__(self):
        return 'str {}'.format(self._name)

    def __repr__(self):
        return 'repr {}'.format(self._name)

    def detail_info(self):
        print('Current ID = {}'.format(id(self))) # 고유의 id 값이 출력됨

        print('Studnet Detail Info : {} {} {}'.format(self._name, self._email, self._details))

    def __del__(self): # 오버라이딩
        Student.student_count -= 1

 

1 ) 클래스 변수

:    self 가 없고, 메서드 밖에서 범위를 가지는 함수를 클래스 변수라고 한다.

ex, 위의 코드에서는 student_count  가  클래스 변수이다.

 

- 클래스 변수에 접근할때는 클래스이름 . 변수이름 을 적어야 한다.

Student.student_count += 1

 

2 ) 인스턴스 변수

:  self 가 있고, 메서드 안에서 범윌르 가지는 함수를 인스턴스 변수 라고 한다. 

    def __init__(self, name , number, grade, details , email = None):
        # 인스턴스 변수들
        self._name = name
        self._number = number
        self._grade = grade
        self._details = details
        self._email = email

 

3 ) 인스턴스 변수와 클래스 변수 접근

- 인스턴스 변수 출력

print(student1._name , student2._name)

 

- 클래스 변수 출력

print(student1.student_count)  # 변수로 접근 
print(Student.student_count)   # 클래스로 접근

 

: 사실, student1 에는 student_count 인스턴스가 존재하지 않는다.

그러나, 파이썬은 인스턴스가 없다면 상위 클래스 변수나 부모 클래스 변수로 찾는다. 

 

 

5. 클래스 설명

1 ) id 값 찾기

: 변수를 선언하게 되면, 각 변수에는 하나의 메모리 를 차지하게 된다. 이때, 그 메모리의 주소값을 id(x) 를 통해서 찾을 수 있다.

student1 = Student('cho', 2, 3, {'gender':'M', 'score1':44, 'score2': 80})
student2 = Student('cho', 4, 5, {'gender':'F', 'score1':44, 'score2': 80}, 'lee@naver.com')
 
# id 값이 다르다 != 가지고 있는 값이 다르다. id 값은 메모리에 저장된 장소라고 생각하면 된다.
print(id(student1)) #4553099440
print(id(student2)) # 4553099248

즉, 두개의 변수의 id 값을 출력해보면 다른 장소에 저장됨을 볼 수 있다.

만약, 표면적인 값이 같더라고 저장된 공간의 주소는 다르기 때문에 id 의 값은 다르게 된다. 

 

2 ) id 값과 표면적인 값 비교

print(id(student1) == id(student2)) # False

print(student1 is student2) # 이건 id 를 비교하는 것 # False
print(student1._name == student2._name) # 이건 값을 비교하는 것 # True

: is 를 사용하면, 각 객체의 주소를 비교할 수 있다.

 

 

3) dir 함수와  __dict__ 비교

: 현업에서는 dict 로 보고 없으면 dir 을 본다고 한다 . dir 이 더 자세하나, 출력의 양이 많다.

 

- dir 함수

: 변수가 가지고 있는 속성을 다 보여준다.

print(dir(student1))

# ['__class__', '__del__', '__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__', '_details', '_email', '_grade', '_name', 
# '_number', 'detail_info', 'student_count']

 

- __dict__

: 인스턴스 속성 값도 가지고 있음. 

print(student1.__dict__)
# {'_name': 'cho', '_number': 2, '_grade': 3, 
# '_details': {'gender': 'M', 'score1': 44, 'score2': 80}, '_email': None}

 

4) __doc__ 

: Doctstring. 클래스에 대한 주석을 볼 수 있다.

print(Student.__doc__)

#  Student Class
#   Author : LEE
#   Data : 2021.05.21

 

5 )  메소드 함수 사용 

class Student():
    """
    Student Class
    Author : LEE
    Data : 2021.05.21
    """

    def __init__(self, name , number, grade, details , email = None):
        self._name = name
        self._number = number
        self._grade = grade
        self._details = details
        self._email = email
        
    def detail_info(self):
      print('Current ID = {}'.format(id(self))) # 고유의 id 값이 출력됨

      print('Studnet Detail Info : {} {} {}'.format(self._name, self._email, self._details))


student1 = Student('cho', 2, 3, {'gender':'M', 'score1':44, 'score2': 80})

 

- 실행 

student1.detail_info()

 

- 클래스로 직접 접근해도, 호출이 가능하다.

Student.detail_info(student1)

# 에러
# Student.detail_info() 
# TypeError: detail_info() missing 1 required positional argument: 'self'

 

6. 인스턴스 메소드 

: self 를 통해서 어떤것을 return 해주는 함수를 인스턴스 메소드라고 함

class Student(object):
	...
    
    # 클래스 변수
    tuition_per = 1.0
    
    # 인스턴스 메소드
    def get_fee(self): # 얼만큼 등록금을 내는가 ?
        return 'Before Tuition -> id : {} , fee : {}'.format(self._id, self._tuition)

    # 인스턴스 메서드
    def get_fee_culc(self):
        return 'After Tuition -> id : {} , fee : {}'.format(self._id, self._tuition * Student.tuition_per)

 

! 클래스 메서드를 쓰기 전의 코드

print(student1.get_fee())      # 학비 인상 전 - 정보
Student.tuition_per = 1.2	   # 학비 인상 률
print(student2.get_fee_culc())  # 학비 인상 후 - 정보 

: 값은 바뀌게 되지만, 위에 코드처럼 직접 값에 접근해서 바꾸는것은 좋지 않다.

: Student.tuituion_per 은 클래스 변수로, 모든 변수가 바라보고있는 변수이다. 

  따라서, 이 값이 쉽게 바뀌게 되며 좋지 않다. 즉, 보호되어야 하고 캡슐화 되어야한다.

 

-> 클래스 변수를 캡슐화 하기 위해서 아래의 클래스 메소드를 사용하게 된다. 

 

7. 클래스 메소드

: 아래와 같은 클래스 메소드 아래 로직도 넣을 수 있다. 

: 혹은, student_construct () 처럼 인스턴스 변수들을 구성해줄 수 있다. 

: 클래스 메소드를 이용하여, 파이써닉 한 코드를 작성할 수 있다. ( 보통 잘만들어진 코드들은 클래스 메서드를 선언한다고 한다. )

 class Student(object):

    # Class Variable
    tuition_per = 1.0
    
    ...
    
 	# 클래스 메서드 
    @classmethod # 데코레이터
    def raise_fee(cls , per): # cls 는 클래스가 넘어옴을 의미함 - 여기서는 Student 와 동일
        if per <= 1:
            print('Please Enter 1 or More')
            return
        cls.tuition_per = per  # Student.tuition_per = per 도 같은 의
        print('Succed ! tuition increased !  ')

    # 클래스 매서드
    @classmethod
    def student_construct(cls, id, first_name, last_name, email , grade, tuition , gpa ):
        return cls(id, first_name, last_name, email , grade, tuition  * cls.tuition_per, gpa)

 

! 클래스 메서드를 사용한 코드

print(student1.get_fee())      # 학비 인상 전 - 정보

Student.raise_fee(1.2)	   # 학비 인상 률
print(student2.get_fee_culc())  # 학비 인상 후 - 정보 

 

- 클래스 메소드 인스턴스 생성 

: 아래처럼 클래스 메소드를 사용해서 변수를 생성하는 것이 분명해서 더 좋은 코드가 된다.

student3 = Student.student_construct(3, 'Park' ,'minji', 'st@naver', '3', 550, 4.0 )

 

 

8. 스테틱 메소드 

! 스테틱 메소드를 사용하지 않는 코드

: 이렇게 코드를 class 에 묶지 않고 따로 함수를 생성할 수는 있지만, 같은 내용인데 따로있으면 함수를 고칠때 번거로울 수 있다.

# 장학금 혜택 여부 ( 스테틱 메소드 미사용 )
def is_scholarship(inst):
    if inst._gpa >= 4.0 :
        return '{} is a scholarship recipient.'.format(inst._last_name)
    return 'Sorry , Not a scholarship recipient'

print(is_scholarship(student1))   # aram is a scholarship recipient.

 

! 스테틱 메소드를 사용한 코드

: 클래스도, 인스턴스랑도 관련이 없을 경우에 사용 

: 스태틱 메소드를 사용하게 되면 접근하는 방법이 유연해진다. ( 2가지 방법 )
 : 스태틱 메소드는 cls, self 로 넘겨받지 않는다.  cls, self 상관없이 넘겨받는 인자로만 받는다.
    < 요즘은 이것이 별로 효율성이 없다!? 라는 의견이 있기도 하다 >

class Student(object):
	... 
	#스테이틱 메서드 
    @staticmethod
    def is_scholarship_st(inst):
        if inst._gpa >= 4.0:
            return '{} is a scholarship recipient.'.format(inst._last_name)
        return 'Sorry , Not a scholarship recipient'

# 1 . 클래스로 접근 가능
print(Student.is_scholarship_st(student1))

# 2. 인스턴스로도 접근 가능 
print(student1.is_scholarship_st(student1))

 

---

: 패스트 캠퍼스 강의를 공부하고 정리한 내용입니다.