파이썬에서 리터럴은, 변수나 상수에 할당되는 원본 데이터(raw data)를 말한다. 이 데이터 자체는 변경되지 않는다.
정수 리터럴, 실수 리터럴, 복소수 리터럴 3가지가 있다.
자료형은 다음장에서 자세하게 배우게 됩니다.
[ ... ]
로 감싸져 있으면 list자료형( ... )
로 감싸져 있으면 tuple자료형{ 키:값, ... }
로 감싸져 있으면 dictionary자료형{ ... }
로 감싸져 있으면 set자료형for 문은 다음과 같이 사용할 수 있다.
for 변수명 in Collection:
반복 실행할 명령문
else:
반복 종료될때 실행되는 명령문
for 에도 else 써서 반복 종료시 실행되는 명령문 작성 가능.
*
를 인자에 붙여 사용한다.*args
를 이용한다.*args
로 인자를 받으면 함수안에서 args
를 튜플
로 사용할 수 있다.def first_name(*names):
for name in names:
print(name[1:])
first_name('차범근', '박지성', '손흥민')
**
를 인자에 붙여 사용한다.**kwargs
를 이용한다.kwargs
는 함수내에서 사전으로 사용되며 param=value
가 key
,value
쌍이 된다.def name_value(**kwargs):
for key, val in kwargs.items():
print(key, val)
name_value(name="Paul", age="10")
일급 객체는 객체지향프로그래밍에서 사용되는 개념 중 하나로 아래의 조건을 만족하는 객체를 의미한다.
Python 에서 함수는 First-Class citizen 다.
아래에서 예제로 함수가 어떻게 First-Class citizen 인지 살펴보자.
def add(a, b):
return a + b
add2 = add # 함수를 변수에 담을 수 있다.
print(add2(12, 7)) # 19
print(add1(12, 7)) # 19
def mul(a, b):
return a * b
cal = [add, mul] # 함수를 자료구조에 담을 수 있다.
for i in cal: # 함수를 i 변수에 할당할 수 있음
print(i(7, 12))
def add(a, b):
return a + b
def add_manager(func, a, b): # 함수를 매개변수로 받음
print(f"add {a} + {b}")
return func(a, b)
res = add_manager(add, 7, 19)
print(res)
def hello(name): # 1
def printer(): # 2
print(f"Hello {name}!") # 3
return printer # 4
func = hello("Fox") # 5
func() # 6
클로저는 어떤 함수의 내부 함수가 외부 함수의 변수를 참조 할때 외부 함수가 종료된 후에도 내부 함수가 외부함수의 변수를 참조할 수 있도록 어딘가에 저장하는 함수를 의미한다.
def hello(msg):
message = "Hello, " + msg
def say():
print(message)
return say
f = hello('Python') # hello 함수가 say 함수를 반환
f() # Hello, Python 출력
def calulate_4(num):
sum_1 = 4 + num
print(sum_1)
def add_5():
print(add(sum_1, 5))
return add_5
add_5 = calulate_4(10) #14
add_5() # 19
hello
함수가 say
함수를 리턴하면서 메모리 스택에서 삭제된다. 이때 hello
함수의 내부 변수였던 message
는 삭제되지 않고 say
함수에 남아있다. 이는 중첩함수인 say
가 외부 함수의 변수인 message
를 참조하기 때문에 message
변수와 say
환경을 저장하는 클로저가 동적으로 생성되었고 f
가 실행될때는 해당 클로저를 참조하여 message
값을 실행하기 때문이다.
데코레이터는 함수를 인자로 받아 꾸며준 후 다시 함수로 리턴하는 함수다.
여기서 꾸며준다는 의미는 인자로 받은 함수 실행 전 주로 특정 로직을 추가하거나 하는 행동이다.
데코레이터를 작성하고 @데코레이터 방식으로 함수를 데코레이팅 할 수 있다.
아래는 함수의 실행시간을 측정해주는 데코레이터를 작성한 코드다.
import time
def logging_duration(orig_func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = orig_func(*args, **kwargs)
end_time = time.time()
print(f"{orig_func.__name__} duration : {end_time - start_time} sec")
return result
return wrapper
@logging_duration
def hello_and_sleep():
time.sleep(1)
print('heelo')
@logging_duration
def hello_and_slee2():
time.sleep(2)
print('heelo2')
hello_and_sleep()
hello_and_slee2()
클래스이름
과 같은 키워드로 해당 클래스의 인스턴스가 새로 생성될 때, 자동으로 호출되는 메소드다. 이 생성자를 이용해서 인스턴스가 생성될 때 수행할 동작을 코드로 짤 수 있는데, 대표적으로 인스턴스 변수를 초기화 하는 용도로 사용한다.__init__
을 이용하여 클래스에서 생성자를 정의할 수 있다.class Person:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
def print_age(self):
print(self.age)
def full_name(self):
return self.last_name + self.first_name
def introduce(self):
print(f"안녕하세요 {self.full_name()} 입니다.")
person1 = Person("철수", "김", 27)
person2 = Person("영희", "이", 28)
person1.print_age()
person2.print_age()
__init__
의 이름으로 정의된 메서드는 정의하게 되면 인스턴스를 생성할때 동시에 실행된다.
위의 예제에서 __init__
은 person1 = Person("철수", "김", 27) 로 인스턴스를 생성하는 시점에 실행된다. 다음과 같이 __init__
을 정의해보자.
...
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
print(f"{last_name}{first_name} : {age}")
...
person1 = Person("철수", "김", 27) # 김철수 : 27
person2 = Person("영희", "이", 28) # 이영희 : 28
위와 같이 __init__
메서드를 정의하면 인스턴스를 생성하는 시점에 이름 : 나이
에 대한 정보가 출력된다.
추가로 함수의 매게변수에 초기값을 선언하는 방법을 이용해 생성자 메서드에 초기값을 선언할 수 있다.
어떤 클래스를 만들때 다른 클래스의 기능을 물려받을 수 있게 만드는 것을 상속이라고 한다. 이번에는 상속 개념을 사용해 우리가 만든 Person 클래스를 상속받은 Developer 클래스를 생성해 보자.
class Developer(Person):
pass
...
클래스를 상속하기 위해서는 다음처럼 클래스 이름 뒤 괄호안에 상속할 클래스 이름을 넣어주면 된다.
Developer 클래스는 Person 클래스를 상속했으므로, Person 클래스의 모든 기능을 사용할 수 있다.
developer1 = Developer("철수", "김", 27)
developer1.print_age() # 27
developer1.introduce() # 안녕하세요 김철수 입니다.
상속받은 Person 클래스의 기능을 모두 사용할 수 있음을 확인할 수 있다.
보통 상속은 기존 클래스를 변경하지 않고 기능을 추가하거나 기존 기능을 변경하려고 할 때 사용한다.
"클래스에 기능을 추가하고 싶으면 기존 클래스를 변경하면 되지않나?" 라는 의문이 들 수 있지만, 기존 클래스가 라이브러리 형태로 제공되거나 수정이 허용되지 않는 상황이라면 상속을 사용해야 한다.
파이썬은 자바와 다르게 다중 상속이 가능한 언어다.
super()
라는 키워드를 사용하면 자식클래스 내에서도 부모클래스를 호출 할 수 있다.class Developer(Person):
def introduce(self):
super().introduce()
print(f"안녕하세요 개발자 {self.full_name()} 입니다.")
developer1 = Developer("철수", "김", 27)
developer1.print_age()
developer1.introduce()
abc
모듈을 import
하여 ABCMeta
를 상속받아 아래와 같이 사용한다.from abc import *
class 추상클래스명(metaclass=ABCMeta):
@abstractmethod
def 추상메소드(self):
pass
class Person(metaclass=ABCMeta):
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
def print_age(self):
print(self.age)
def full_name(self):
return self.last_name + self.first_name
def introduce(self):
print(f"안녕하세요 {self.full_name()} 입니다.")
@abstractmethod
def print_job(self):
print("직업은?")
위와같이 Person 객체에 추상메소드를 선언한경우 Person을 상속받은 Developer 객체에서 추상메소드를 재정의 해야한다.
class Developer(Person):
def print_job(self):
print("개발자로 일하고 있다.")
class ProductManager(Person):
def print_job(self):
print("PM으로 일하고 있다.")
자주 사용되는 Magic 메소드에 대해 알아보자.
__new__
: 새로운 객체를 만들 때 제일 처음으로 실행되는 메소드__init__
: 객체 생성자, 객체를 생성할 때 사용됨__str__
: str(객체) 의 결과값을 정의__repr__
: 객체를 표현할때 사용하는 문자열 정의가능__add__
: 두개의 객체를 더하려고 할때 사용__and__
: & 연산자를 사용하려고 할때 실행__bool__
: 객체에 대해 부울 검사를 수행할 때 실행됨__mul__
: * 연산자를 사용하려고 할때 사용__mod__
: / 연산자를 사용하려고 할때 사용__le__
: ≤ 에 사용__ge__
: ≥ 에 사용__del__
: 객체를 없앨때 호출되는 메소드__len__
: 객체의 길이를 반환__doc__
: Docstring 을 출력하는 메소드Person 객체에서 나이를 이용하여 __add__
와 __ge__
를 구현한 예시는 다음과 같다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __add__(self, other):
return self.age + other.age
def __ge__(self, other):
return self.age >= other.age
person1 = Person("철수", 27)
person2 = Person("영희", 28)
print(person1 + person2) # 55
print(person1 >= person2) # False
Public attribute 의 경우 언더스코어를 붙이지 않는다
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
Protected attribute 의 경우 한개의 언더스코어를 이용한다.
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
Private attribute 의 경우 두개의 언더스코어를 이용하여 표현한다.
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age
raise
명령어를 사용해 오류를 강제로 발생시킬 수 있다.예를 들어 Person 클래스를 상속받는 자식 클래스는 반드시 introduce 메소드를 구현해야 한다고 생각해 보자.
class Person:
def introduce(self):
raise NotImplementedError
Person 클래스를 상속받은 자식 클래스가 introduce 를 오버라이딩 하여 에러발생 부분을 없애지 않는다면 자연스럽게 NotImplementedError
가 발생한다.
class Developer(Bird):
pass
developer1 = Developer()
developer1.introduce()
Person 클래스를 만들때 인자로 받는 age 값이 음수일때 발생시키는 예외를 작성해 보자.
class AgeError(Exception):
def __str__(self):
return "나이는 항상 자연수값을 가져야 한다."
class Person:
def __init__(self, age):
if age <= 0:
raise AgeError
self.age = age
날짜와 시간을 연산할때는 timedelta
클래스를 이용한다. 두개의 datetime
클래스 객체의 차이의 결과가 timedelta
클래스로 반환된다.
timedelta
클래스는 다음과 같은 속성과 메소드를 가진다.
days
: 일수seconds
: 초microseconds
: 마이크로초total_seconds()
: 모든 속성을 초 단위로 모아서 반환import datetime
dt1 = datetime.datetime(2016, 3, 11, 11)
dt2 = datetime.datetime(2022, 6, 2, 11)
td = dt1 - dt2
td
print(td.days, td.seconds, td.microseconds)
print(td.total_seconds())
delta = datetime.timedelta(days=90, seconds=3600)
dt0 = dt1 + delta
Python 에서 기본으로 제공하는 logging 모듈을 이용해 프로그램 실행 중 로그를 남길 수 있다.
import logging
특정 이벤트가 발생했을 때 로그를 심게 되는데, 이벤트의 심각성에 따라 logging level을 설정할 수 있다. 표준 levels 로는 아래 다섯가지가 있다.
import logging
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
# ...
# WARNING:root:This is a warning message
# ERROR:root:This is an error message
# CRITICAL:root:This is a critical message
logging 으로 생성된 메세지는 각 logging level 에 대한 정보도 함께 포함하고 있다.
logging 의 설정값을 정의하는데 basicConfig()
메소드를 사용할 수 있다.
basicConfig()
에 사용할 수 있는 parameter 는 다음과 같다.
level
: logger에서 공개하고 싶은 level을 설정할 수 있다. 예를 들어 warning 으로 하면 info 등급의 로깅은 출력되지 않는다.filename
: 로그를 적을 파일이름을 명시할 수 있다.filemode
: 파일이름이 주어졌을때 파일모드를 정할 수 있다. default 값은 a 다.format
: log message 의 포맷을 결정한다.import logging
logging.basicConfig(filename='app.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s')
logging.warning('This will get logged to a file')
# app.log 파일에 'root - ERROR - This will get logged to a file' 라고 출력된것을 확인할 수 있다.
로그를 작성할때 사용할 수 있는 몇가지 기본 요소들이 있다. 그 요소들을 이용해 logging 포멧을 설정할 수 있다.
process
: process idlevelname
: severity levelmessage
: 로깅 메세지asctime
: 24시간 기준 시계로 시간 출력import logging
logging.basicConfig(format='%(asctime)s.%(msecs)03d - %(levelname)s - %(process)d- %(message)s', datefmt='%d-%b-%y %H:%M:%S')
logging.warning('Admin logged out')
## 31-Dec-22 20:12:06.288 - WARNING - 18472 - Admin logged in
로깅 모듈은 모든 프로그램 내에서 생성되는 stack traces 를 캡쳐하는 기능을 제공한다. 예를 들어 예외와 관련된 정보는 exc_info=True
를 이용하여 출력할 수 있다.
import logging
a = 5
b = 0
try:
c = a / b
except Exception as e:
logging.error("Exception occurred", exc_info=True)
위 코드를 실행하면 다음과 같은 결과가 출력된다.
ERROR:root:Exception occurred Traceback (most recent call last): File "exceptions.py", line 6, in <module> c = a / b ZeroDivisionError: division by zero [Finished in 0.2s]
exc_info=True
외에 logging.exception()
메소드를 이용해 동일한 기능을 수행할 수 있다.
logging.exception()
메소드는 ERROR level 의 로그와 stack traces 를 동시에 출력한다.
import logging
a = 5
b = 0
try:
c = a / b
except Exception as e:
logging.exception("Exception occurred")
위 코드의 실행결과는 다음과 같다.
ERROR:root:Exception occurred Traceback (most recent call last): File "exceptions.py", line 6, in <module> c = a / b ZeroDivisionError: division by zero [Finished in 0.2s]
지금까지 logging.debug()
와 같이 함수가 직접 호출될 때 마다 로깅 모듈에서 사용하는 root 라는 기본 로거를 이용했다. 프로그램에 서로 다른 이름의 로거를 사용해야 되는 순간에 어떻게 해야하는지 logging 모듈의 클래스와 함수를 살펴보자.
Logger
: 함수를 호출하기 위해 직접적으로 사용되는 클래스다.LogRecord
: logging 작업에 관련된 모든 정보를 담고 있는 클래스다.Handler
: Handler
는 LogRecord
를 콘솔이나 파일과 같은 필요한 출력 대상으로 보낸다.Formatter
: Formatter
를 이용해 출력 형식을 지정한다.import logging
logger = logging.getLogger('example_logger')
# example_logger 이름의 Logger 객체를 반환한다
logger.warning('This is a warning')
Handler
는 로거를 구성하고 로그가 생성될 때 여러 위치로 로그를 보내려는 경우에 사용된다. Handler
는 로그 메시지를 표준 출력 스트림, 파일, HTTP를 이용해 다른 서버 혹은 SMTP를 통해 이메일로 보낸다.
생성한 로거에는 하나 이상의 Handler
가 있을 수 있다. 즉, 로그 파일에 저장하고 이메일을 통해 보낼 수도 있다.
로거와 마찬가지로 Handler
에서 심각도 수준을 설정할 수도 있다. 이는 동일한 로거에 대해 여러 처리기를 설정하지만 각각에 대해 다른 심각도 수준을 원하는 경우에 유용하다.
예를 들어, 레벨이 WARNING 이상인 로그를 콘솔에 기록하기를 원할 수 있지만 레벨이 ERROR 이상인 모든 로그도 파일에 저장해야 한다. 다음은 이를 수행하는 프로그램이다.
import logging
# 새로운 커스텀 로거 생성
logger = logging.getLogger(__name__)
# handler 생성
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler('file.log')
c_handler.setLevel(logging.WARNING)
f_handler.setLevel(logging.ERROR)
# formatter 를 생성하여 handler 에 부착
c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# handler 를 로거에 부착
logger.addHandler(c_handler)
logger.addHandler(f_handler
logger.warning('This is a warning')
logger.error('This is an error')
fileConfig()
혹은 dictConfig()
를 사용하여 로그 속성을 정의할 수 있다.
아래 두가지 예제를 통해 학습해보자.
[loggers]
keys=root,sampleLogger
[handlers]
keys=consoleHandler
[formatters]
keys=sampleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_sampleLogger]
level=DEBUG
handlers=consoleHandler
qualname=sampleLogger
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=sampleFormatter
args=(sys.stdout,)
[formatter_sampleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
fileConfig()
를 이용한 logger 구성 설정import logging
import logging.config
logging.config.fileConfig(fname='file.conf', disable_existing_loggers=False)
logger = logging.getLogger(__name__)
logger.debug('This is a debug message')
# 2018-07-13 13:57:45,467 - __main__ - DEBUG - This is a debug message
config.yaml
파일을 생성하고 다음 값들을 입력하자
version: 1
formatters:
simple:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
loggers:
sampleLogger:
level: DEBUG
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]
yaml format 에 대한 내용은 다음 링크를 참고
- 설치: pip install PyYAML
- https://realpython.com/python-yaml/
생성한 yaml 파일을 읽어 dict 객체를 생성하고 이를 이용해 logger 를 설정할 수 있다.
import logging
import logging.config
import yaml
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f.read())
logging.config.dictConfig(config)
logger = logging.getLogger(__name__)
logger.debug('This is a debug message')