n331_Python 활용 (02)

ssu_hyun·2021년 10월 2일
0

[codestates] AI Bootcamp

목록 보기
33/62

#PEP 249 #DBAPI #함수 #클래스 #인스턴스

🏆 학습 목표

  • 파이썬 함수를 활용할 수 있어야 합니다.
  • 파이썬 클래스를 활용할 수 있어야 합니다.
  • Pythonic에 대해서 생각할 수 있어야 합니다.



함수(functions)



매개변수; 파라미터(parameter)

함수를 정의할 때 사용되는 소괄호에 들어가는 것들로, 함수는 이를 선택적으로 받을 수 있다. 이는 위치에 따라 영향을 받으므로 파라미터를 어느 순으로 받는지 정한 것에 따라 함수를 호출할 때 순서를 지켜서 인수(argument)를 넘겨야 한다.


❓ 참조 vs 값?

파이썬에서는 인수들이 참조로 함수에 전달이 된다. 즉, 객체의 (메모리)주소값을 전달한다는 뜻. 참조로 전달되지만 immutable, 즉 변경이 불가한, 객체들은 값으로 전달된다. 따라서 int, str 등은 값으로 전달이 되며 list, dict와 같은 객체들은 참조값으로 전달이 되는 것이다.

❓ 입력값이 몇 개가 될지 모를 때는? : *args & **kwargs

대부분 *args**kwargs는 함수를 정의 할 때 사용된다. 이들은 가변 갯수의 인자들을 함수에 넣어주는데 여기서 가변 갯수의 인자란, 사용자들이 얼마나 많은 인자들을 함수에 넣을지 모르는, 즉 갯수가 변할 수 있는 상황에서 *args**kwargs를 사용할 수 있다는 뜻이다.(별표만 꼭 사용하면 되고 이름은 아무렇게나 지어도 된다.(ex. **name))
args = arguments
*kwargs = keyword arguments

###*args
# ex1
def add_many(*args): 
     result = 0 
     for i in args: 
         result = result + i 
     return result
     
# ex2
def add_mul(choice, *args): 
     if choice == "add": 
         result = 0 
         for i in args: 
             result = result + i 
     elif choice == "mul": 
         result = 1 
         for i in args: 
             result = result * i 
     return result 
     
>>> result = add_mul('add', 1,2,3,4,5)
>>> print(result)
15
### 키워드 파라미터 kwargs

# ex1
def print_kwargs(**kwargs):
     print(kwargs)

# 매개변수 kwargs는 딕셔너리가 되고 모든 key=value 형태의 결괏값이 그 딕셔너리에 저장된다.
>>> print_kwargs(a=1)
{'a': 1}
>>> print_kwargs(name='foo', age=3)
{'age': 3, 'name': 'foo'}

인수(arguments)

필수 인수 (required arguments)

위치를 지키며 전달되는 인수

def person_info(first_name, last_name):
    print(f"Hello {first_name}, {last_name}!")
    
# TypeError
person_info('sponge')

키워드 인수 (keyword arguments)

파라미터 이름을 사용해 위치 인수 (positional arguments)로 전달하지 않고 키워드를 사용해 전달되는 인수

person_info(last_name="bob", first_name="sponge")

기본 인수 (default arguments)

인수를 넘기지 않는 경우 파라미터에서 기본으로 사용되는 값
주의) 기본 값으로 설정된 파라미터들은 기본값이 없는 파라미터 뒤에 등장해야 한다

def person_data(name, type_p='human'):
    print(f"Hello {name}, you are {type_p}")

return vs print

  • return : 해당 함수가 어떤 값을 가질 수 있도록 '반환'해주는 것(모니터에 표시되지 않음)
  • print : 원하는 값을 사람이 볼 수 있도록 모니터 상에 '출력'만 하는 용도

함수 데코레이터

  • DRY(Don't Repeat Yourself)
  • 함수를 감싸는 형태로 구성되어 있어 데코레이터는 함수의 내부를 수정하지 않고 추가 기능을 구현할 때 사용 (함수에는 데코레이터 여러 개 지정도 가능)
### decorator
def decorator(func):
    def wrapper(*args, **kwargs):
       
        print('전처리')   
        print(func(*args, **kwargs))
        print('후처리')
    return wrapper
    
@decorator
def example():
    return '함수'
    
    
>>> example()

전처리
함수
후처리

        
### 인자값이 정의되어있을 때
def first_last_deco(func): # func 는 실행할 함수입니다.
    def first_last(*args, **kwargs): # 실행할 함수를 감싸는(wrap) 함수입니다.
        print("first")
        func(*args, **kwargs)
        print("last")
    return first_last

@first_last_deco
def you(name):
    print(f"{name}! Hello")



클래스(class)

객체 지향 프로그래밍(Object Oriented Programming)

  • 모듈 : 복잡한 여러 함수들을 grouping한 수납공간
  • 객체(object) = class(종류 : 포유류) + instance(사례, 예제 : 사람, 고양이, 말)
  • 클래스(class) : 연관된 함수(로직, 메소드, 행위)와 변수(데이터, 속성, 필드, 상태)를 grouping한 것
  • 인스턴스(instance) : class를 복제한 것으로 class와 같은 함수와 변수를 가지나 변수의 값은 다르다.(그러므로 각 instance의 값이 다르다) 인스턴스 변수는 인스턴스마다 다르고, 이를 사용하는 일반 메소드는 인스턴스마다 서로 다른 값을 출력하게 된다. 다시 말해 일반 메소드는 인스턴스에 종속적인 결과를 출력한다.
# ruby
name1 = String.new('egoing')
name2 = String.new('k8805')
puts(name1.reverse())  # gnioge
puts(name2.reverse())  # 5088k
name = 'egoing' # egoing = 객체
names = ['egoing', 'k8805'] # 배열이면서 객체
name1 = String.new('egoing') # string 클래스를 복제하여 String.new('egoing')이라는 새로운 instance를 만들어 name1이라는 변수에 저장하여 사용(즉, name1이라는 instance가 생성된 것)
puts(name1.reverse()) # name1이라는 변수가 가리키는 instance의 reverse라는 함수를 실행한 것
  • 메소드(method) : 객체에 소속되어 있는 함수
  • 생성자(__init__) : 클래스가 인스턴스화(instantiate) 될 때 자동으로 실행되도록 약속되어있는 초기화와 관련된 메소드로 인스턴스의 초기 속성들을 받을 수 있다. 하지만 이는 인스턴스마다의 속성이지 클래스 전체에 대한 속성은 아니므로 클래스 자체에서는 이러한 속성들에 대한 접근이 힘들다.
# 계산기 만들기
class Cal(object):
    # 생성자 메소드 
    # 첫번째 매개변수 self = instance 이므로 반드시 정의
    def __init__(self, v1, v2):
    	self.v1 = v1  # 인스턴스 전체에서 사용할 수 있는 변수가 됨 (전역변수같이..)
        self.v2 = v2
    def add(self):
    	return self.v1 + self.v2
	def subtract(self):
    	return self.v1 - self.v2


Cal.v1   #=> AttributeError: type object 'Cal' has no attribute 'v1'

c1 = Cal(10,10)
print(c1.add())
print(c1.subtract())

c2 = Cal(30,20)
print(c2.add())
print(c2.subtract())

*self
일반 메소드들에는 첫 인자로 ‘self’를 꼭 넣어준다. ‘self’를 암시적으로(implicitly) 넣어줌으로써 일반 메소드가 인스턴스의 변수에 접근할 수 있다. 인스턴스를 지칭하는 단어는 꼭 ‘self’이여야 할 필요는 없지만 첫 인자로 인스턴스 자신을 의미하는 인자는 어떤 식으로든 꼭 넣어줘야 한다

  • 객체를 사용하는 이유
    • programming의 복잡도 해결, 코드 정리
    • 코드 상에서 함수간의 연관성 분명히 드러남
    • 변수와 메소드가 다른 코드들의 영향을 받아 훼손/변형될 가능성이 낮다



클래스 특별 메소드

@property

  • 클래스를 만들고 해당 클래스 특성들을 설정해 주는 메소드
  • 클래스 특성들을 생성할 때 self 를 통해서 생성해 줄 수도 있지만 여러 변수가 연결되거나 특별히 관리가 필요한 경우 등 이러한 상황들에서는 따로 함수로 작동하는 방식으로 관리해 줄 수 있다.
  • 클래스 내에 다른 특성들과 연관이 되어 있는 특성들을 관리할 때 사용
class Person:
	def __init__(self, first_name, last_name):
		self.first_name = first_name
		self.last_name = last_name

	@property
	def full_name(self):  
		return self.first_name + ' ' + self.last_name


fred = Person('Fred', 'Williams')

fred.first_name = 'Ted'  # 인스턴스 변수의 동적 변화에 유연

print(fred.first_name) #=> 'Ted'
print(fred.full_name) #=> 'Ted Williams'  # 클래스의 특성(attribute)처럼 접근 가능   

getter, setter

  • @property를 사용하게 되면 클래스의 특성으로 만들어주는 것도 좋지만 여기서 더 나아가 해당 특성을 가져오거나 값을 설정할 때 사용하는 메소드를 통해 더 섬세한 관리를 할 수 있다.
### ex1
# full_name 을 설정하게 되면 first_name 을 바꿔주는 방법
class Person:
	def __init__(self, first_name, last_name):
		self.first_name = first_name
		self.last_name = last_name

	@property  # getter의 역할
	def full_name(self):
		return self.first_name + ' ' + self.last_name
		
	@full_name.setter  # full_name을 변경할 때 어떻게 바꾸는지 알려주는 함수
	def full_name(self, new_full_name):
		first_name, last_name = new_full_name.split()
		self.first_name = first_name
		self.last_name = last_name
        
    
 ### ex2
 class Citizen:

    def __init__(self, age_value):
        self._age = age_value

    @property
    def age(self):
        print("나이를 리턴합니다.")
        return self._age

    @age.setter
    def age(self, age_value):
        print("나이를 새로 설정합니다.")
        self._age = age_value

>>> citizen = Citizen(20)
>>> citizen.age = 30
>>> print(citizen.age)
        
나이를 새로 설정합니다.
나이를 리턴합니다.
30
                
# @property 가 붙은 age 메소드(A)가 있으면 Citizen 클래스의 인스턴스.age , 이 코드가 실행될 때 A가 실행
# @age.setter 가 붙은 age 메소드(B)가 있으면 Citizen 클래스의 인스턴스.age = 어떤 숫자값 이 코드가 실행될 때 B가 실행
# 실제로 나이를 나타내는 인스턴스 변수 = _age

_ & __

변수나 함수에 특별한 의미를 부여할 때 사용

_(single underscore)

파이썬 클래스 내부에서 따로 변수나 값을 저장할 때 사용

class Pokemon:
	_pokemon_health = 100 

    def __init__(self, pokemon_a='pikachu'):
        self.pokemon_a = pokemon_a

poke_a = Pokemon()
print(poke_a._pokemon_health) #=> 100
# 클래스 내부 변수나 값으로 사용하더라도 파이썬에서는 외부에서 접근 가능하다

__ (double underscore, dunderscore)

  • 파이썬 클래스 내부에서만 관리하고 싶을 때 사용
  • _<클래스 이름>__<변수 혹은 함수 이름>
class Pokemon:
	__pokemon_health = 100

    def __init__(self, pokemon_a='pikachu'):
        self.pokemon_a = pokemon_a

poke_a = Pokemon()
print(poke_a.__pokemon_health) #=> 에러(Name Mangling)
print(poke_a._Pokemon__pokemon_health) #=> 100

컨벤션

파이썬 코딩 컨벤션



객체와 변수

캡슐화(encapsulation)

  • 객체 안에 포함되어있는 데이터가 외부로부터 영향을 받지 않도록어떤 부품(==객체)들을 객체, 모듈, 함수와 같은 캡슐에 넣는 것.
  • 데이터는 소중하다 고로 변수는 소중하다 => 변수가 외부 영향을 받지 않도록 하는 것은 매우 중요

set/get 메소드

### ex1
class Cal(object):
    def __init__(self, v1, v2):
        if isinstance(v1, int):  # 생성자로 유입되는 데이터 값을 검증
            self.v1 = v1	 # v1이 int일 경우에만 실행됨
        if isinstance(v2, int):
            self.v2 = v2
    def add(self):
        return self.v1+self.v2
    def subtract(self):
        return self.v1-self.v2
    def setV1(self, v):
        if isinstance(v, int):
            self.v1 = v
    def getV1(self):
        return self.v1
        
c1 = Cal(10,10)
print(c1.add())
print(c1.subtract())
c1.setV1('one')
c1.v2 = 30
print(c1.add())
print(c1.subtract())


### ex2
class Citizen:
    def __init__(self, age_value):
        self._age = age_value

    def get_age(self):
        print("나이를 리턴합니다.")
        return self._age

    def set_age(self, age_value):
        print("나이를 새로 설정합니다.")
        self._age = age_value


citizen = Citizen(20)
print(citizen.get_age())
citizen.set_age(25)
print(citizen.get_age())

>>> 나이를 리턴합니다.
>>> 20
>>> 나이를 새로 설정합니다.
>>> 나이를 리턴합니다.
>>> 25
  • 인스턴스 변수의 값에 직접 접근하여 값을 바꾸는 것이 가능하다.(외부에서 인스턴스 value로 접근이 가능함)

=> 어떻게하면 불가능하게 할 것인가?

class C(object):
    def __init__(self, v):
        self.__value = v  ## __붙이기
    def show(self):
        print(self.__value)
c1 = C(10)
print(c1.__value) # 불가능
c1.show()  # print이므로 가능

상속(inheritance)

기존 객체 변수,함수 + 새로운 기능
이미 만들어져있는 객체의 변수와 함수에 새로운 기능을 추가해 새로운 객체를 만들어내는 행위

ex)
객체1 = 자전거
객체2 = 분위기 있는 자전거 객체
객체3 = 강력 브레이크 자전거 객체

class Class1(object):
    def method1(self): return 'm1'
c1 = Class1()
print(c1, c1.method1())

# 상속 클래스
class Class3(Class1):  # Class3(자식)가 Class1(부모)을 상속한다
    def method2(self): return 'm2'
c3 = Class3()
print(c3, c3.method1()) # m1 => 부모 기능 상속 & 값의 변화 자동으로 영향 받음
print(c3, c3.method2())

# 미상속 클래스
class Class2(object):
    def method1(self): return 'm1' # m1 => 다시 한번 정의해주어야하며 값의 변화 영향 못 받음
    def method2(self): return 'm2'
c2 = Class2()
print(c2, c2.method1())
print(c2, c2.method2())

>>> <__main__.Class1 object at 0xb757dfec> m1
>>> <__main__.Class3 object at 0xb758704c> m1
>>> <__main__.Class3 object at 0xb758704c> m2
>>> <__main__.Class2 object at 0xb758708c> m1
>>> <__main__.Class2 object at 0xb758708c> m2

*실행결과가 어떤코드로부터 나왔는지 알 수 있는 print 기능 (입력값 2개 할당)

*상속 받은 클래스 인수값(argument)


클래스 멤버/정적 메소드

  • 클래스에 소속되어 있는 변수, 메소드 (!=인스턴스 멤버)
  • 정적 메소드 : 클래스에서 직접 접근할 수 있는 메소드(객체에서 접근 가능)로 때에 따라 각각의 인스턴스와 상관 없는, 다시 말해 인스턴스마다 값이 항상 동일한(인스턴스마다 값이 달리지지 않는), 또는 클래스 단위로 관리해야 할 변수나 메소드이다.
  • 인스턴스 멤버 : 인스턴스에 따라 다르게 동작해야 하는 메소드 (인스턴스 \supset 메소드)
  • 클래스 멤버 : 인스턴스의 내부적인 데이터와는 상관없이 동작하는 메소드
    • @classmethod : 클래스 단위로 관리되는 클래스 변수에 접근 (클래스의 모든 인스턴스마다에서 같은 값을 다루고 출력)
    • @staticmethod : 클래스 안에 있지만 일반 함수와 다를게 없는 메소드라서 클래스의 인스턴스에서 호출할 수 있다는 것외에는 차이점이 없다. 클래스 자신을 지칭하는 첫 인자를 받지 않기에 인스턴스의 상태는 물론 클래스의 상태와도 상관 없는 메소드가 된다. 보통은 어떤 클래스의 유틸리티 메소드로 많이 쓰인다.
### 클래스 변수
class Human:
    # 인류의 학명은 모든 인스턴스마다 동일하다!
    # 따라서 클래스 변수로 'species' 할당
    species = 'Homo Sapiens'  # 클래스 변수 = 클래스 안, 메소드 밖에 존재

    def __init__(self, name, age):
        ...

C = Human('Charles Darwin', 30)

>>> print(Human.species)
>>> print(C.species)  # C라는 이름의 인스턴스에서도 클래스 변수에 접근 가능


Homo Sapiens
Homo Sapiens


### 클래스 메소드
class Cat:
    species = 'Felis Catus'
    
    @classmethod  # 데코레이터로 클래스메소드 정의
    def get_species(cls):  # 첫 인자로 클래스를 뜻하는 ‘cls’라는 변수를 암묵적으로 받는다.
        print(f'Scientific name for cat is {cls.species}!')  # ‘cls’를 통해 클래스 변수에 접근 가능

>>> print(Cat.get_species())

Scientific name for cat is Felis Catus!


### 클래스 멤버
class Cs:
    @staticmethod  # 클래스 멤버
    def static_method():
        print("Static method")
    @classmethod  # 클래스 멤버
    def class_method(cls):  # cls 반드시 있어야 함
        print("Class method")
    def instance_method(self):  # 인스턴스 멤버
        print("Instance method")

i = Cs()
Cs.static_method() # 클래스 소속
Cs.class_method()  # 클래스 소속
i.instance_method() # 인스턴스 소속


### 클래스 메소드와 스태틱 메소드의 차이
class Language:
    default_language = "English"

    def __init__(self):
        self.show = '나의 언어는' + self.default_language

    @classmethod
    def class_my_language(cls):
        return cls()

    @staticmethod
    def static_my_language():
        return Language()

    def print_language(self):
        print(self.show)


class KoreanLanguage(Language):
    default_language = "한국어"
    
>>> from language import *
>>> a = KoreanLanguage.static_my_language()  # staticmethod :부모클래스의 클래스 속성 값을 가져옴
>>> b = KoreanLanguage.class_my_language()  # classmethod : cls인자를 활용해 cls의 클래스 속성을 가져옴
>>> a.print_language()
나의 언어는English
>>> b.print_language()
나의 언어는한국어


### 클래스 멤버 활용(_history)
class Cal(object):
    _history = []
    def __init__(self, v1, v2):
        if isinstance(v1, int):
            self.v1 = v1
        if isinstance(v2, int):
            self.v2 = v2
    def add(self):
        result = self.v1+self.v2
        Cal._history.append("add : %d+%d=%d" % (self.v1, self.v2, result))
        return result
    def subtract(self):
        result = self.v1-self.v2
        Cal._history.append("subtract : %d-%d=%d" % (self.v1, self.v2, result))
        return result
    def setV1(self, v):
        if isinstance(v, int):
            self.v1 = v
    def getV1(self):
        return self.v1
    @classmethod
    def history(cls):
        for item in Cal._history:
            print(item)
class CalMultiply(Cal):
    def multiply(self):
        result = self.v1*self.v2
        Cal._history.append("multiply : %d*%d=%d" % (self.v1, self.v2, result))
        return result
class CalDivide(CalMultiply):
    def divide(self):
        result = self.v1/self.v2
        Cal._history.append("divide : %d/%d=%d" % (self.v1, self.v2, result))
        return result
        
        
c1 = CalMultiply(10,10)
print(c1.add())
print(c1.multiply())
c2 = CalDivide(20,10)
print(c2, c2.add())
print(c2, c2.multiply())
print(c2, c2.divide())
Cal.history()

오버라이드(override)

부모 객체의 기능을 재정의해 바꾸는 것(말 그대로 덮어씌우는 것)

### super()
class C1:
    def m(self):
        return 'parent'
class C2(C1):
    def m(self):
        return super().m() + ' child'  # super().m() = parent
    pass
o = C2()
print(o.m())  # parent child


### 활용(info)
class Cal(object):
    _history = []
    def __init__(self, v1, v2):
        if isinstance(v1, int):
            self.v1 = v1
        if isinstance(v2, int):
            self.v2 = v2
    def add(self):
        result = self.v1+self.v2
        Cal._history.append("add : %d+%d=%d" % (self.v1, self.v2, result))
        return result
    def subtract(self):
        result = self.v1-self.v2
        Cal._history.append("subtract : %d-%d=%d" % (self.v1, self.v2, result))
        return result
    def setV1(self, v):
        if isinstance(v, int):
            self.v1 = v
    def getV1(self):
        return self.v1
    @classmethod
    def history(cls):
        for item in Cal._history:
            print(item)
    def info(self):		
        return f"Cal => v1 : {self.v1}, v2 : {self.v2}"
class CalMultiply(Cal):
    def multiply(self):
        result = self.v1*self.v2
        Cal._history.append("multiply : %d*%d=%d" % (self.v1, self.v2, result))
        return result
    def info(self):
        return "CalMultiply => %s" % super().info()  # 오버라이드
class CalDivide(CalMultiply):
    def divide(self):
        result = self.v1/self.v2
        Cal._history.append("divide : %d/%d=%d" % (self.v1, self.v2, result))
        return result
    def info(self):
        return "CalDivide => %s" % super().info() # 오버라이드
 
 
c0 = Cal(30, 60)
print(c0.info())  # Cal => v1 : 30, v2 : 60

c1 = CalMultiply(10,10)
print(c1.info())  # CalMultiply => Cal => v1 : 10, v2 : 10

c2 = CalDivide(20,10)
print(c2.info())  # CalDivide => CalMultiply => Cal => v1 : 20, v2 : 10

모듈

연관성있는 객체들을 grouping 해놓은 수납공간

### .py
import lib
obj = lib.A()  # 클래스 A 인스턴스화
print(obj.a()) # obj의 메소드 a 호출 => a(리턴값)

### lib.py
class A:
    def a(self):
        return 'a'

다중상속

하나의 클래스가 여러 클래스의 기능을 상속 받는 것

class C1():
    def c1_m(self):
        print("c1_m")
    def m(self):
        print("C1 m")
 
class C2():
    def c2_m(self):
        print("c2_m")
    def m(self):
        print("C2 m")
 
class C3(C2, C1):  # 다중 상속(앞쪽 우선순위 높음)
    def m(self):
        print("C3 m")
 
c = C3()  
c.c1_m()  # c1_m
c.c2_m()  # c2_m
c.m()     # C2 m # method가 중복될 경우 문제 발생 => 다중상속 제한적 사용
print(C3.__mro__) # class 우선순위 print



[reference]

객체 지향 프로그래밍(Object-Oriented Programming, OOP) Series
생활코딩
점프 투 파이썬_클래스
@property 사용하기
[코드잇] 쉽게 배우는 파이썬 문법 - 프로퍼티(Property)
self 이해하기
[Youtube] 파이썬 코딩 무료 강의

0개의 댓글