Programming Language/Python

python - class/ 상속/ magic method

HJChung 2020. 4. 17. 10:50

Class

1. 객체지향이란 : 설계도(Class) 작성 -> 실제 물건(객체; Object)

'사용자 정의 데이터 타입'이라고도 한다.

사용 이유: 여러명의 개발자가 코드를 효율적으로 작성해서 프로젝트를 완성시키기 위한 방법

2. Class란 : 변수와 함수를 묶어놓은 개념이라고 간단하게 생각

Class의 사용 방법:

- 변수와 함수가 들어있는 Class를 선언
- Class를 객체로 만들어서 Class안에 선언된 변수와 함수를 사용한다. 

- 변수와 함수가 들어있는 Class를 선언
- Class를 객체로 만들어서 Class안에 선언된 변수와 함수를 사용한다. 
#1. Class의 선언 - 계산기 class
class Calculator:
    #변수 작성
    num1 = 1
    num2 = 2
    
    #함수 작성
    def plus(self): # class에 선언되는 모든 함수는 가장 첫번째 parameter로 self를 사용한다
        return self.num1 + self.num2
    
    def minus(self):
        return self.num1-self.num2
    

 

self란 객체 자신을 말한다. 

1) class의 사용

 Class를 객체로 만들어서 Class안에 선언된 변수와 함수를 사용한다.

calc = Calculator() #Class명() 을 해 줍니다. calc: Class가 있는 calc객체가 선언된다. 
calc #결과: <__main__.Calculator at 0x110431d10>
 

2) 객체 내의 변수 확인 

#2. 객체 내의 변수 확인
calc.num1, calc.num2, calc.plus() #실행 결과: (1, 2, 3)

 

3. 생성자: Class가 객체로 만들어질 때 객체에 선언된는 변수를 설정하는 방법

Class가 변수(재료)를 추가할 때 사용된다. 그래서 주로 생성자 함수 안에, 사용할 변수를 선언한다.

위의 Class Calculator 는 객체 생성 후 바로(무조건) num1=1, num2=2가 생성되어 나중에 객체 선언할 때 수정까지 해 주어야한다.

그래서 위와 달리, 객체 선언시에 변수를 만들어주고 싶다하면, 생성자인 __init__ 를 사용한다.

1) 생성자(__init__)의 사용

    def __init__(self, num1, num2):
        self.num1 = num1
        self.num2 = num2

그래서 다시 class Calculator을 생성자를 사용해서 위의 코드를 바꿔보면 아래와 같이 쓸 수 있다. 

 
# 3. 생성자(__init__)을 사용하여 객체 선언시에 변수 생성
class Calculator:
    def __init__(self, num1, num2): #def__init__(self, num1, num2=10): 처럼 default 값도 쓸 수 있고 *args, **kargs 도 사용가능
        
        self.num1 = num1
        self.num2 = num2
        
    def plus(self): 
        return self.num1 + self.num2
    
    def minus(self):
        #pass
        return self.num1 - self.num2
    
    
calc2 = Calculator(3, 6)
calc2.plus() #실행결과: 9
 
 

예제) 

# Class 예제 : 스타크래프트의 마린을 클래스로 설계
# 체력(health), 공격력(attack_pow), 공격(attack())
# 마린 클래스로 마린 객체 2개를 생성해서 마린1이 마린2를 공격하는 코드를 작성해보시오

class Marine:
    def __init__(self, health, attack_pw):
        self.health = health
        self.attack_pw = attack_pw
        
    def attack(self, you):#나(self)가 상대방(you)를 공격한다. 
        return you.health - self.attack_pw
    
    
#객체 선언
my_marine = Marine(40,5)
your_marine = Marine(40,5)

my_marine.attack(your_marine)
my_marine.health, your_marine.health #실행결과: 40, 35
        
 

4. 상속: Class의 기능을 그대로 가져다가 기능을 수정/추가 할 때 사용하는 방식

1) 사용 방법:  class 클래스 명(상속 받을 것)

#4. 상속

class Calculator:
    def __init__(self, num1, num2): #def__init__(self, num1, num2=10): 처럼 default 값도 쓸 수 있고 *args, **kargs 도 사용가능
        
        self.num1 = num1
        self.num2 = num2
        
    def plus(self): 
        return self.num1 + self.num2
    
    def minus(self):
        #pass
        return self.num1 - self.num2
    
    
#Calculator2는 Calculator에 곱하기 기능을 추가한 것이라면,
class Calculator2(Calculator): # class 클래스 명(상속 받을 것)
    #이러면 Calculator기능이 다 들어오고
    #추가/수정 할 것만 여기에 적어주면 된다. 
    
    #추가
    def mul(self): 
        return self.num1 * self.num2
    
    #수정(재정의): 메서드 오버라이딩을 사용하여 수정한다. 
    def plus(self):
        return self.num1 + self.num2 + 10
    
    
    
cal3 = Calculator2(2, 4)
cal3.plus(), cal3.minus(), cal3.mul() #실행 결과 16, -2, 8

5. Super: 부모 class에서 사용된 함수의 코드를 다시 자식의 class에서 재사용할 때 사용되는 것

바로 위의 코드에서 잠깐 상속해서 overriding해서 다시 수정해서 쓰는 것 까지 배워봤는데,

부모 class에서 사용된 함수의 코드를 다시 자식의 class에서 재사용할때 사용되는 것이 super이다. 


1) 사용 방법:  

class A:
  def plus(self):
      code1

 

class B(A):
  def minus(self):
      code1 # -> super().plus()
      code2

이런 코드가 있다고 할때, 자식 class인 B에 부모 class의 code1이 중복(동일)하다. 이럴 때, 이를 code1로 쓰는게 아니라 super().plus() 라고 쓰면 A의 code1이 그대로 이 위지에 적용된다.

그리고 뭔가를 수정할 때도, class A에서 code1만 수정해주면 된다.

 

예를들어

 

이런 코드가 있을 때
class Marine:
    
    def __init__(self):
        self.health = 40
        self.attack_pw = 5
        
    def attack(self, unit):
        unit.health -= self.attack_pw
        if unit.health <= 0:
            unit.health = 0

여기서 maxhealth를 추가하여 Marine2를 만들고 싶다면, 

 
#여기서 maxhealth를 추가하여 Marine2를 만들고 싶다면, 
class Marine2(Marine):
    #수정: overriding
    def __init__(self):
        self.health = 40 #이거랑
        self.attack_pw = 5 #이게 중복된다. 
        self.max_health = 40
        
        
        
#이를 super을 이용해서 대체해 줄 수 있다.
class Marine2(Marine):
    
    def __init__(self):
        super().__init__() #super().뭘 그대로 상속받을지 
        self.max_health = 40
        
        
my_marine = Marine2()
my_marine.health, my_marine.attack_pw, my_marine.max_health #실행 결과: 40, 5, 40
 

6. class의 getter, setter: 객체의 내부 변수에 접근할 때 특정 로직을 거쳐서 접근시키는 방법

제약조건을 걸어주어 특정 함수의 로직을 거쳐서 변수를 넣어주는 것이 getter, setter이다.
 
이런 경우를 생각해보자. 
class User:
    def __init__(self, first_name, last_name):
            self.first_name = first_name
            self.last_name = last_name
            
    def disp(self):
        print(self.first_name, self.last_name)
 

이 코드가 적절하게 사용된다면 이런 식일 것이다. 

user1 = User("andy","kim")
user1.first_name = "Jone"

user1.disp()#실행결과: Jone kim

그런데 실수로라도 이렇게 이름에 숫자를 적는 등의 적절하지 않게 사용할 수도 있다.

user1.last_name = 99
user1.disp() 
#이럴 경우, 문자만 들어가야하는데 필터나 규제를 걸어놓지 않아서 임의의 형식이 들어갈 수 있게 되어놨다. 그래서, 
# 실행결과: Jone 99
 
위와 같은 경우를 막기 위해 getter, setter을 사용하여 제약 조건을 걸어줄 수 있다. 
# 제약조건을 걸어주어 특정 함수의 로직을 거쳐서 변수를 넣어주는겍 getter, setter이다. 
class User:
    def __init__(self, first_name):
            self.first_name = first_name
            
    def setter(self, first_name): #저장시
       #setter에 제약조건 달아주기:  
        if len(first_name) >= 3: #first_name은 3글자 이상이어야해~ 안그럼 에러다~
            self.first_name = first_name
        else:
            print("error")
            
    def getter(self, first_name): #출력시
        print("getter")
        return self.first_name
            
    def disp(self):
        print(self.first_name, self.last_name)
    name = property(getter, setter) #property: getter, setter에 print처럼 접근할 수 있도록 도와주는 함수
        
user1 = User("Grace")
 
#getter함수 실행
user1.name = "Grace" #setter함수 실행 #good!
 
user1.name = "Jo" #에러 발생
 
 

7. is a & has a : 클래스를 설계하는 개념

  • A is a B
    • A는 B이다. 상속을 이용해서 클래스를 만드는 방법
#class 사람: 이름, 이메일, 정보출력()을 is a 로 만들기

class Person():
    def __init__(self, name, email):
        self.name = name
        self.email = email
class Person2(Person):
    def info(self):
        print(self.name, self.email)
 
p = Person2("Grace", "hey@gmail.com")
p.info() #실행결과: Grace hey@gmail.com
 
  • A has a B
    • A는 B를 가진다. A가 B객체를 가지고 클래스를 만드는 방법
#class 사람: 이름, 이메일, 정보출력()을 has a 로 만들기

class Name:
    def __init__(self, name):
        self.name_str = name
        
class Email:
    def __init__(self, email):
        self.email_str = email
 
class Person:
    def __init__(self, name_obj, email_obj):
        self.name = name_obj
        self.email = email_obj
        
    def info(self):
        print(name.name_str, email.email_str)
 
my_name = Name("Grace")
my_email = Email("he__y@gmail.com")
p = Person(name, email)
 
p.info() #실행 결과: Grace he__y@gmail.com
 
In [80]:
'test' == 'test' #문자열 객체와의 비교 방법1. == 쓰기
#실행결과: True
'test'.__eq__('test') #문자열 객체와의 비교 방법2. __eq__ magic method
#실행결과: True
 
 
1+2, #이건 3,

"1"+"2" #이건 '12'
#이 둘이 다르게 작동하는 이유는, +는 int 객체에 있는 add함수와, str 객체에 있는 add함수가 다르게 작동하기 때문이다. 
 
#직접 class 만들어보자. 
class Txt:
    def __init__(self, txt):
        self.txt= txt
        
t1 = Txt("python")
t2 = Txt("python2")
t3 = t1
        
 
t1==t2, t1==t3, t2==t3
# 실행 결과 False, True, False
 

9. 클래스 변수 인스턴스 변수 

# 클래스 구조
# 구조 설계 후 재사용성 증가, 코드 반복 최소화, 메소드 활용

class Student():
    # 해당 class에 대한 설명을 """로 설명을 달아두는 것이 좋다. 
    """
    Student Class
    Author: Grace Chung
    Date: 2021.02.17
    """
    
    #클래스 변수
    student_count = 0

    def __init__(self, name, number, grade, details):
    	#인스턴스 변수
    	self._name = name
        self._number = number
        self._grade = grade
        self._details = details
        
      	#클래스명을 기반으로 클래스 변수에 접근
        Student.student_count += 1
  
        
    def __str__(self):
   	    return 'str: {}'.format(self._name)
        
   	#클래스 메소드
    def detail_info(self):
    	print('Current Id: {}'.format(id(self)))
        print('Student Detail Info : {} {} {}'.format(self._name, self._details))
        
    def __del__(self):
    	Student.student_count -= 1
        
    
    
stud1 = Student('Kim', 1, 1, {'gender': 'Male', 'score1': 95, 'score2': 100})
stud2 = Student('Kim', 2, 2, {'gender': 'Female', 'score1': 90, 'score2': 80})
stud3 = Student('Lee', 3, 4, {'gender': 'Female', 'score1': 70, 'score2': 60})


print(stud1) #실행결과: str: Kim (__str__을 안해줬다면 <__main__.Student object at 0x00001D3B1110898> 이렇게 객체형식으로 나온다.)  
print(stud1.__dict__) #실행결과: {'Kim', 1, 1, {'gender': 'Male', 'score1': 95, 'score2': 100}}


# ID 확인
print(id(stud1))
print(id(stud2))

print(stud1._name == stud2._name)
print(stud1 is stud2)

# dir & __dict__의 사용
print(dir(stud1))
print(stud1.__dict__)

# Doctring의 사용
print(Student.__doc__)

# 실행
stud1.detail_info()
# 또는 class로 직접 접근하여서 메소드를 실행시킬 수도 있다. Student.detail_info(stud1)

# 해당 인스턴스의 클래스 확인하기
print(stud1.__class__) #실행결과: <class '__main__.Student'>

# 현업에서는 인스턴스 변수에 직접 접근하는 것을 PEP 문법적으로 권장하지 않는다.
stud1._name = 'Park'
print(stud1._name) #이렇게 stud1의 인스턴스 변수인 _name에 직접 접근 하여 수정 것을 권장하지 않는다. 
# 그래서 권장하는 것은 캡슐화

# 인스턴스 네임스페이스 없으면 상위에서 검색
# 즉, 동일한 이름으로 변수 생성 가능 (인스턴스 검색 후 --> 상위(클래스 변수, 부모 클래스 변수))를 찾는 순서니까.