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

1

제어문

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 의 프록시가 생성되기에
Abar() 메서드가 호출되면서 위와같은 문자열이 찍히는 것

이번엔 다중상속에서 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

다중상속에서 superMRO 가 알아서 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 이동이 가능하다.