#PEP 249 #DBAPI #함수 #클래스 #인스턴스
🏆 학습 목표
함수를 정의할 때 사용되는 소괄호에 들어가는 것들로, 함수는 이를 선택적으로 받을 수 있다. 이는 위치에 따라 영향을 받으므로 파라미터를 어느 순으로 받는지 정한 것에 따라 함수를 호출할 때 순서를 지켜서 인수(argument)를 넘겨야 한다.
파이썬에서는 인수들이 참조로 함수에 전달이 된다. 즉, 객체의 (메모리)주소값을 전달한다는 뜻. 참조로 전달되지만 immutable, 즉 변경이 불가한, 객체들은 값으로 전달된다. 따라서 int, str 등은 값으로 전달이 되며 list, dict와 같은 객체들은 참조값으로 전달이 되는 것이다.
대부분 *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'}
위치를 지키며 전달되는 인수
def person_info(first_name, last_name):
print(f"Hello {first_name}, {last_name}!")
# TypeError
person_info('sponge')
파라미터 이름을 사용해 위치 인수 (positional arguments)로 전달하지 않고 키워드를 사용해 전달되는 인수
person_info(last_name="bob", first_name="sponge")
인수를 넘기지 않는 경우 파라미터에서 기본으로 사용되는 값
주의) 기본 값으로 설정된 파라미터들은 기본값이 없는 파라미터 뒤에 등장해야 한다
def person_data(name, type_p='human'):
print(f"Hello {name}, you are {type_p}")
### 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")
# 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라는 함수를 실행한 것
__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’이여야 할 필요는 없지만 첫 인자로 인스턴스 자신을 의미하는 인자는 어떤 식으로든 꼭 넣어줘야 한다
@property
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)처럼 접근 가능
@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
변수나 함수에 특별한 의미를 부여할 때 사용
파이썬 클래스 내부에서 따로 변수나 값을 저장할 때 사용
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
# 클래스 내부 변수나 값으로 사용하더라도 파이썬에서는 외부에서 접근 가능하다
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
### 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
=> 어떻게하면 불가능하게 할 것인가?
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이므로 가능
기존 객체 변수,함수 + 새로운 기능
이미 만들어져있는 객체의 변수와 함수에 새로운 기능을 추가해 새로운 객체를 만들어내는 행위
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)
@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()
부모 객체의 기능을 재정의해 바꾸는 것(말 그대로 덮어씌우는 것)
### 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] 파이썬 코딩 무료 강의