python 기본문법!
자료형
파이썬의 특이한 자료형에 대해 알아본다.
문자열
a = 'python'
# 문자열 연산
a * 2 # pythonpython
"=" * 50 # ==================================================
'hi' + 3 # TypeError: can only concatenate str (not "int") to str > 오히려 될것 같은건 안됨
# 문자열 인덱스
a[3] # 'h'
a[-2] # 'o' 뒤에서 2번째
# 문자열 슬라이스
a[0:3] # 'pyt'
a[:3] # 'pyt' default start 0
a[3:] # 'hon' defatlt end EOL
a[1:-1] # 'ytho'
# 문자열 포멧팅
"hello %s" % "wolrd" # 'hello wolrd'
"hello %02d" % 3 # 'hello 03'
"hello {0}".format("world") # 'hello world'
"my name is {name} and {age} age".format(name="kouzie", age=20) # 'my name is kouzie and 20 age'
리스트
# 리스트 연산
a = [1,2,3]
b = [4,5,6]
a+b # [1, 2, 3, 4, 5, 6]
a*2 # [1, 2, 3, 1, 2, 3]
a.extend(a) # [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
# 요소 추가
a = [1,2,3]
a.append(4) # [1, 2, 3, 4]
a.insert(0,5) # [5, 1, 2, 3, 4] (index, value)
# 요소 삭제
b = [4,5,6,7]
b.remove(5) # [4, 6, 7] (index)
del b[2:] # [4, 6]
c = b.pop() # c=6 b=[4]
# 요소 정렬
a = [2,3,1,4]
a.sort() # [1, 2, 3, 4]
b = ['b','c','a','d']
b.sort() # ['a', 'b', 'c', 'd']
b.reverse() # ['d', 'c', 'b', 'a']
# 요소 탐색
b = ['a', 'b', 'c', 'd', 'c']
b.index('c') # 2, first index
b.count('c') # 2, 개수
딕셔너리
# 추가
a = {"hi": "hello"}
a["who"] = "me" # {'hi': 'hello', 'who': 'me'}
# 삭제
del a['hi'] # {'who': 'me'}
a.clear() # { } 초기화
# 조회
a = {'hi': 'hello', 'who': 'me'}
a.keys() # dict_keys(['hi', 'who'])
a.values() #dict_values(['hello', 'me'])
a.items() # dict_items([('hi', 'hello'), ('who', 'me')])
list(a.keys()) # ['hi', 'who'] # 리스트로 변환
list(a.values()) # ['hello', 'me']
list(a.items()) # [('hi', 'hello'), ('who', 'me')] # 튜플에 대한 리스트
items = a.items()[0][0] # 'hi'
tuple, set
list
와 비슷한 형태의 자료형이지만
tuple
은 초기화 이후 요소의 변경이 불가능 ()
기호를 사용해 생성한다.
set
은 순서없고 중복 불가능한 집합 자료형, {}
기호를 사용해 생성한다.
t = (1,2,3)
s = {1,2,3}
# list to tuple, set
t = tuple([1,2,3])
s = set([1,2,3])
# set 연산
s1 = {1,2,3,4}
s2 = {3,4,5,6}
s1 & s2 # {3, 4} 교집합
s1 | s2 # {1, 2, 3, 4, 5, 6} 합집합
s1 - s2 # {1, 2} 차집합
# set 요소 추가
s1 = {1,2,3,4}
s1.add(5) # {1, 2, 3, 4, 5}
s1.update([6,7,8]) # {1, 2, 3, 4, 5, 6, 7, 8}
# set 요소 삭제
s1.remove(3) # (index)
s1.pop()
bool
제어문
if & 조건문
x = True
y = False
if x:
print("hi")
elif y:
print("hello")
else:
print("world")
bool
자료형 끼리는 and
, or
, not
x and y # False
x or y # True
not y # True
리스트, 튜플을 검사할 땐 in
, not in
1 in [1,2,3] True
1 not in [2,3,4] True
while
while x:
if y:
break
if z:
continue
파이썬에는 do while 이 없음
for
test_list = ['one', 'two', 'three']
for i in test_list:
print(i)
# one
# two
# three
for (k, v) in a.items():
print("key:%s, value:%s" % (k, v))
# key:hi, value:hello
# key:who, value:me
in
연산자를 통해 요소를 꺼내 반복문 실행
함수
def add(a=1, b=1):
return a + b
def add_all(*args):
result = 0
for i in args:
result += i
return result
add() # 2
add(b=2,a=5) # 7
add_all(1,2,3,4) # 10
python 은 별도의 반환값, 매개변수의 인자값 등을 명시하지 안하도 된다.
변수 scope
파이썬도 타 언어와 같이 변수의 scope
를 가지는데, global
, local
두개로 나뉜다.
모듈단위는 global
함수단위는 local
함수 중첩시 nonlocal
scope
도 존재하긴 한다.
global_var = "전역 변수"
def outer():
nonlocal_var = "비전역 변수"
print(global_var) # 가능
print(nonlocal_var) # 가능
def inner():
local_var = "지역 변수"
print(global_var) # 가능
print(nonlocal_var) # 가능
print(local_var) # 가능
print(local_var) # 불가능 (NameError: name 'local_var' is not defined)
print(nonlocal_var) # 불가능 (NameError: name 'nonlocal_var' is not defined)
print(local_var) # 불가능 (NameError: name 'local_var' is not defined)
이때 scope
를 명확지 지정하기 위해 키워드를 사용가능
global_var = "전역 변수"
def outer():
global global_var
global_var = "새로운 전역 변수"
print(global_var) # 전역 변수
outer()
print(global_var) # 새로운 전역 변수
당연히 scope 를 지정하지 않으면 새로운 변수가 선언되면서 초기화 되지 않음
nonlocal
키워드 역시 새로운 변수 선언을 하지 않고 상위 중첩함수의 변수를 가져오기 위한 키워드
클래스
클래스 정의 구문은 아래와 같다.
class Calculator:
def __init__(self):
self.result = 0
def add(self, num1, num2):
self.result = self.result + num1 + num2
return self.result
c1 = Calculator()
c1.add(1, 2) # 3
c1.add(3, 4) # 10
c1.result # 10
Calculator.add(c1, 5, 6) # 21
클래스 이름은 CamelCase, 함수와 변수명은 SnakeCase 로 사용하는 것이 정석
default
에 숫자를 중첩해서 저장 후 반환
self
키워드를 사용하고 싶다면 항상 클래스 함수 첫 인자로 지정
self
를 생략하려면 인스턴스에서 메서드를 호출
클래스를 통해 메서드를 직접 호출면 지정해야함
클래스변수, 생성자, 메서드오버라이딩
클래스변수는 모든 인스턴스, 클래스 가 공유하는 전역변수
class Car:
# 클래스변수
brand = 'hyundai'
# 셍성자
def __init__(self, name):
self.name = name
def print_name(self):
print(self.name)
print(Car.brand) # hyundai
c1 = Car('sonata')
c1.print_name()
# c2 = Car()
# TypeError: Car.__init__() missing 1 required positional argument: 'name'
class Truck(Car):
def __init__(self, name, size):
super().__init__(name)
self.size = size
# 메서드오버라이딩
def print_name(self):
print("truck name:", self.name)
def print_size(self):
print(self.size)
Truck.brand = 'kia'
print(Truck.brand) # kia
t1 = Truck('bongo', 10)
t1.print_name() # truck name: bongo
t1.print_size() # 10
# c2 = Car()
파이썬에는 메서드오버로딩
을 지원하지 않기때문에 생성자나 함수에 여러개의 변수를 받아야 할 경우 *args
, **kwargs
를 매개변수로 받아야한다.
class CountCalls(object):
def print_str(self, *args, **kwargs):
print("print args", args.__str__(), "kwargs", kwargs.__str__())
if __name__ == '__main__':
cc = CountCalls()
cc.print_str(1, 2, 3) # print args (1, 2, 3) kwargs {}
cc.print_str(name="kouzie", age=20) # print args () kwargs {'name': 'kouzie', 'age': 20}
*
(아스타리스크)는 튜플 형태의 인자값(복수개)
**
두개는 딕셔너리 형태의 인자값(맵)을 뜻한다.
args
에는 ('hello', 'world')
kwargs
에는 {'username': 'kouzie', 'age': 20}
__init__, __call__
class CountCalls(object):
def __init__(self, num1, num2):
self.num1 = num1
self.num2 = num2
print("init invoked", self.num1, self.num2)
def __call__(self, keyword1, keyword2):
print("call invoked", keyword1, keyword2)
if __name__ == '__main__':
cc1 = CountCalls(1, 2) # init invoked, 1, 2
cc1("test", "print") # call invoked test print
__init__
은 생성자 호출시 실행
__call__
은 인스턴스 자체 호출시 실행
상속
class Pizza:
def get_name(self):
return "pizza"
class CheesePizza(Pizza):
def get_name(self):
return "cheese"
if __name__ == '__main__':
p = CheesePizza()
print(p.get_name())
클래스정의시 상속할 클래스명을 삽입하여 진행한다.
메서드 오버라이딩 개념역시 적용된다.
메서드해석순서(MRO) - 다중상속
먼저 메서드해석순서(MRO:method resolution order) 에 대해 알아보자.
파이썬은 MRO
에 따라 클래스간의 상속트리(함수호출순서)를 구축한다.
클래스정의 또한 파이썬에선 일종의 참조변수에 존재하는 데이터로써 클래스관련 동작을 할 때 마다 해당 참조변수로부터 값을 가져와 동작시킨다.
그래서 아래같은 클래스정의 참조변수를 사용한 이상한 코드 역시 정상 동작한다.
def parent():
return object # 파이썬 가장 기본클래스 를 가리키는 참조변수
class Child(parent()):
pass
if __name__ == '__main__':
print(Child.mro())
# [<class '__main__.Child'>, <class 'object'>]
MRO
의 출력결과를 보면 배열안에 Child, object
가 순서대로 들어가 있다.
이번엔 다중상속시에 MRO
구성을 확인해보면
class A(object):
def foo(self):
pass
class B(object):
def bar(self):
pass
class C(A, B):
def baz(self):
pass
if __name__ == '__main__':
print(C.mro())
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
C, A, B, object
순서대로 들어가 있다.
메서드가 호출되면 MRO 상속트리를 기준으로 호출할 메서드를 찾는다.
super
파이썬에서 super
는 상위객체의 생성자를 호출하는 것과 비슷하다.
super
를 호출할 때 마다 상위객체의 프록시객체인 super<상위객체>
가 생성된다.
super(A, self)
이런식으로 많이 호출하는데
첫번째 인수는 클래스 정의
두번째 인수는 하위클래스 혹은 첫번째 클래스의 인스턴스이다.
class A(object):
def bar(self):
return "AAA"
class B(A):
def bar(self):
return "BBB" + super(B, self).bar()
if __name__ == '__main__':
b = B()
print(b.bar()) # BBBAAA
super
로 생성되는 객체는 첫번째 인수로 전달한 클래스의 상위클래스 에 대한 프록시객체로 동작하는데
B
클래스의 상위클래스인 A
의 프록시가 생성되기에
A
의 bar()
메서드가 호출되면서 위와같은 문자열이 찍히는 것
이번엔 다중상속에서 super
, 그리고 MRO 가 어떻게 동작하는지 알아보자.
class A(object):
def foo(self):
return "foo"
class B(object):
def foo(self):
return "bar"
def bar(self):
return "bar"
class C(A, B):
def foo(self):
return "baz" + super(C, self).foo()
def bar(self):
return "baz" + super(C, self).bar()
if __name__ == '__main__':
c = C()
print(c.foo()) # bazfoo
print(c.bar()) # bazbar
다중상속에서 super
는 MRO
가 알아서 2개의 클래스를 하나의 클래스를 상속하는 것 처럼 만들어 주기 떄문에
상위 클래스의 foo
, bar
메서드를 모두 자유롭게 호출 가능하고
두 상위클래스에서 겹치는 메서드가 있을 경우 먼저 검색되는 클래스의 메서드를 사용한다.
Python3
부터super
함수의 인자가 사라졌다,super().foo()
형태로 호출 가능
메서드
아래 2종류의 메서드에 대해 알아보자.
- 정적메서드
- 클래스메서드
class Pizza(object):
# 클래스변수
radius = 42
def __init__(self, size):
self.size = size
# 일반메서드
def get_size(self):
return self.size
# 정적메서드
@staticmethod
def mix_ingredients(x, y):
return x + y
# 클래스메서드
@classmethod
def get_radius(cls):
return cls.radius
데커레이터 설정으로 간단히 정의 가능하다.
클래스메서드 특징
- 객체생성 없이 바로 호출 가능
cls
클래스바인딩을 사용해 클래스 상태(변수)에 접근가능- 주로 펙토리 패턴으로 객채생성 해야할 경우 사용함
# 클레스메서드를 사용한 펙토리 패턴
@classmethod
def factory(cls, init_dict):
return cls(init_dict.name, init_dict.age)
클레스메서드는 좀더 기능이 추가된 정적메서드라 볼 수 있다.
추상메서드
추상메서드는 하위 클래스가 재정의하지 않으면 오류를 발생하기 위한 개념으로 자주 사용한다.
class Pizza(object):
def get_name(self):
raise NotImplementedError
class CheesePizza(Pizza):
def get_name(self):
return "cheese"
if __name__ == '__main__':
# p = Pizza() error invoked
p = CheesePizza()
print(p.get_name())
JIT
언어인 파이썬에서 Pizza
객체를 생성했다고 조기에 에러를 발생시키지 않기에 쉽게 버그가 발생한다.
이때 유용하게 사용할 수 있는 패키지가 abc(abstract base class)
import abc
class Pizza(object, metaclass=abc.ABCMeta):
@abc.abstractmethod
def get_name(self):
"""TODO method overriding"""
if __name__ == '__main__':
p = Pizza() # TypeError: Can't instantiate abstract class Pizza with abstract method get_name
생성자를 호출하자마자 에러가 발생한다.
@staticmethod @classmethod
를 사용해 정적인 추상메서드 생성이 가능하다.
class BasePizza(Pizza):
pass
class CheesePizza(Pizza):
@staticmethod
def get_name():
return "cheese"
class BaconCheesePizza(CheesePizza):
pass
if __name__ == '__main__':
# p0 = BasePizza() 에러 발생
p1 = CheesePizza()
p2 = BaconCheesePizza()
Pizza
의 하위클래스들에서 정적메서드 이후의 클래스부터는 추상메서드 오류가 발생하지 않는다.
모듈
모든 .py
확장자의 파일은 모듈로서 동작 가능
mod1.py
파일을 정의하고
# mod1.py
def add(a=1, b=1):
return a + b
def add_all(*args):
result = 0
for i in args:
result += i
return result
if __name__ == "__main__":
print("hello world")
커맨드창에서 바로
mod1.py
를 실행하면__name__
전역변수가__main__
으로 처리되어 조건문 내부 코드가 실행됨
타 파일에서 import
, from
을 통해 파일 내부에 함수, 클래스 정의를 땡겨올 수 있다.
# test.py
import mod1
from mod1 import add_all
print(mod1.add(1,2)) # 3
print(add_all(1,2,3)) # 6
가져오고 싶은 함수, 클래스를 콤마(
,
) 로 이어 모두 가져오거나 아스타리스크(*
)를 통해 전부 가져올 수 있다.
모듈 경로
모듈이 같은레벨 경로에 있으면 .py
파일명만 써도 import
가능하지만 하위 디렉토리에 저장되어 있다면 디렉토리명.파일명
형식으로 경로를 지정해줘야 한다.
내장함수
별다른 경로 설정없이 사용할 수 있는 함수는 이미 sys
경로에 지정되어 있기에 바로 쓸 수 있는것
아래 sys
모듈을 통해 경로를 확인 가능
import sys
sys.path
# ['', 'C:\\Windows\\SYSTEM32\\python37.zip', 'c:\\Python37\\DLLs', 'c:\\Python37\\lib', 'c:\\Python37', 'c:\\Python37\\lib\\site-packages']
해당 sys.path
에 디렉토리경로를 지정하면 직접 작성한 모듈도 바로 import
가능하다.
패키지
디렉토리 만들고 내부에 __init__.py
파일 만들고
내부에 모듈로서 동작할 각종 .py
파일을 만들면 그게 패키지다.
패키지를 import
함으로 내부에 정의된 많은 .py
파일의 함수들은 사용 가능하다.
아래와 같이 game 디렉토리가 구성되어 있을 때
$ tree game
.
├── __init__.py
├── graphic
│ ├── __init__.py
│ └── render.py
└── sound
├── __init__.py
└── echo.py
# game/sound/echo.py
def echo_test1():
print('echo1')
def echo_test2():
print('echo2')
def echo_test3():
print('echo3')
다양한 방식으로 정의된 함수, 클래스를 가져올 수 있다.
- 패키지 import
- 모듈 import
- 모듈 내부의 함수 import
# test.py
import game.sound.echo
from game.sound import echo
from game.sound.echo import echo_test3
game.sound.echo.echo_test1()
echo.echo_test2()
echo_test3()
__init__.py
해당 디렉토리가 패키지에 포함되는 것음 의미
__all__
변수를 지정함으로 아스타리스크(*
) 을 통해 import
할 모듈을 직접 지정 할 수 있다.
# __init__.py
__all__ = ['echo']
from game.sound import *
형식으로 모듈을 가져오도록 지정할 때 __init__.py
의 __all__
변수에 echo
모듈이 지정되어 있음으로 사용 가능하다.
한 뿌리의 패키지에 포함되어 있다면 relative 경로 (..
) 를 통해 depth
를 이동하거나
한 뿌리의 패키지라면 상위 디렉토리의 이름을 지정하여 패키지의 depth
이동이 가능하다.