sugenius

파이썬 개발 역량을 높이는 파이썬 문법과 특징, 코딩 스타일 본문

Python

파이썬 개발 역량을 높이는 파이썬 문법과 특징, 코딩 스타일

sugeniusk 2021. 3. 22. 20:42

도서 파이썬 알고리즘 인터뷰 3장 중

파이썬 문법

파이썬의 공식 인터프리터 Interpreter인 CPython 기준. (PyPy등 .. 여러 있음)

내부구현을 살펴보며 원리를 정확히 이해한다. 

 

| 인덴트 Indent 

공식 가이드인 PEP8(www.python.org/dev/peps/)에 따라 공백 4칸 원칙

파이참과 같은 개발도구 활용시 자동으로 맞춰줌 

 

def long_funchion_name(
        var_one, var_two, var_three,
        var_four) : 
    print(var_one)
    
foo = long_funchion_name(var_one, var_two ,
                          var_three, var_four)

 

| 네이밍 컨벤션 Naming Convention 

자바와 달리 각 단어를 _로 구분하여 표기하는 스네이크 케이스 Snake Case를 따름. 함수명도 마찬가지. 

카멜 케이스 (단어별로 대소문자를 구별하여 표기) 뿐만 아니라 자바 스타일로 코딩하는 것을 지양함. 

 

# 카멜 케이스 
camelCase : int = 1 

# 스네이크 케이스 
snake_case : int = 1 

 

| 타입 힌트 Type Hint

파이썬은 대표적 동적 타이핑 언어임에도, 타입 지정할 수 있는 타입 힌트가 있다. (파이썬 3.5 ver ~ 사용 가능)

# 함수 정의 
def fn(a) :
	... 

#타입 힌트
def fn(a: int) => bool: 
	...

빠르게 정의하여 사용할 수 있다는 장점이 있지만 파라미터 a에는 숫자를 넘겨야 할지, 문자를 넘겨야 할지 전혀 알수없고 리턴값이 무엇인지 알 수 없다. 프로젝트 규모가 커지게 되면 가독성을 떨어뜨리며 버그 유발의 주범이 된다. 

 

타입 힌트를 사용하게 되면 함수의 파라미터 a가 정수형임을 분명히 알수 있으며 리턴 값으로 True 또는 False 를 리턴할 것이라는 점도 확실히 알 수 있다. 

 

+ 온라인 코딩 테스트 시 mypy를 사용하면 타입 힌트에 오류가 없는지 자동으로 확인 가능. pip으로 설치 가능. 

타입 힌트가 잘못 지정됐을 때, Incompatible return value type 오류 발생하므로 확인후 수정 

 

| 리스트 컴프리헨션 List Comprehension 

파이썬은 map, filter와 같은 함수형 기능을 지원, 람다 표현식 Lambda Expression도 지원함. 

List(map(lambda x:x+10, [1,2,3])) 

#홀수인 경우 2를 곱해 출력하라는 리스트 컴프리헨션
[n*2 for n in range (1,10+1) if n%2 == 1]
#출력 : [2,6,10,14,18]

#리스트 컴프리헨션을 사용하지 않으면 길게 풀어서 작성해야 함 
a = []
for n in range(1,10+1) :
	if n%2 == 1 :
    	a.append(n*2)

#리스트 컴프리헨션이라고 반드시 리스트만 가능한 것은 아님. 
#리스트 외 딕셔너리 등이 가능 
a = {} 
for key, value in original.items() :
	a[key] = value 

#딕셔너리 컴프리헨션
a = {key : value for key, value in original.items()}

 

| 제너레이터 Generator 

루프의 반복 동작을 제어할 수 있는 루틴 형태. 

예를 들어 임의의 조건으로 숫자 1억개를 만들어 내 계산하는 프로그램을 작성할때 

제너레이터가 없다면 메모리 어딘가에 만들어낸 숫자 1억개를 보관하고 있어야 한다. 

그러나 제너레이터를 이용하면, 단순 제너레이터만 생성해두고 필요할 떄 언제든 숫자를 만들어 낼 수 있다. 

이때 yield 구문을 사용하면 제너레이터를 리턴할 수 있다. 기존의 함수는 return 구문을 맞닥뜨리면 값을 리턴하고 모든 함수의 동작을 종료한다. 그러나 yield는 제너레이터가 여기까지 실행 중이던 값을 보낸다는 ( 사전적 의미 '양보하다')의미로, 중간값을 리턴한 다음 함수는 종료되지 않고 계속해서 맨 끝에 도달할 때 까지 실행된다. 

While True 구문은 종료 조건이 없으므로 계속해서 값을 내보낼 수 있다. 

def get_natural_number():
    n = 0
    while True:
        n += 1
    yield n


g = get_natural_number()
for _ in range(0, 100):
    print(next(g))

'''
1
2
3
. .
99
100
'''

#여러 타입의 값을 하나의 함수에서 생성하는 것도 가능 
def generator():
    yield 1
    yield 'string'
    yield True
g= generator()
next(g) #1 
next(g) #'string'
next(g) #True

 

| range

제너레이터의 방식을 활용하는 대표적인 함수.

range(5) 는 range 클래스를 리턴. for문에서 사용할 경우 내부적으로 제너레이터 next()를 호출하듯 매번 다음 숫자를 생성해내게 됨. 

>>> list(range(5))
[0, 1, 2, 3, 4]
>>> [0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
>>> range(5)
range(0, 5)
>>> type(range(0,5))
<class 'range'>
>>> for i in range(5) :
	print(i, end='')

	
01234

 

| enumerate 

enumerate() '열거하다'는 뜻의 함수. 여러가지 자료형 (list, set, tuple 등)을 인덱스를 포함한 enumerate 객체로 리턴. 

>>> a = [1,2,3,2,45,2,5]
>>> a
[1, 2, 3, 2, 45, 2, 5]
>>> enumerate(a)
<enumerate object at 0x02FA8F68>
>>> list(enumerate(a))
[(0, 1), (1, 2), (2, 3), (3, 2), (4, 45), (5, 2), (6, 5)]

 

a=['a1','b2','c3']
for i in range(len(a)):
    print(i,a[i])
    
i=0
for v in a :
    print(i,v)
    i+=1
    
for i, v in enumerate(a):
    print(i,v)

 

| // 나눗셈 연산자 

나눗셈 연산자 / 와 동일한 역할. 정수형을 나눗셈할 때 동일한 정수형을 결과로 리턴하면서 내림 Floor Division 연산자 역할. 다시 말해 몫 Quotient를 구하는 연산자. 

5//3 은 int (5/3)과 동일함. 정수형 결과가 필요한 나눗셈 연산에 사용. 

몫과 나머지를 동시에 구하려면 divmod() 함수 이용.

>>> 5 /3
1.6666666666666667
>>> type(5/3)
<class 'float'> # //나눗셈 연산자는 int (a/b) 와 동일함.
>>> 5//3
1
>>> type(5//3)
<class 'int'>
>>> type(int(5//3))
<class 'int'>

#나머지
>>> 5%3
2

# 몫과 나머지를 한번에 구할때 divmod()
>>> divmod(5,3)
(1, 2)

 

| print

값을 쉽게 출력하려면 콤마(,)로 구분한다. 이 경우 한 칸 공백이 디폴트로 설정되어 있다. 

sep 파라미터로 구분자를 콤마로 구분해 줄수도 있다.

print()함수는 항상 줄바꿈을 하기 때문에 긴 루프의 값을 반복적으로 출력하면 디버깅하기 어려운데, 이경우 end 파라미터를 공백으로 처리하여 줄바꿈을 하지 않도록 제한할 수 있다.

리스트를 출력할 때는 join() 으로 묶어서 처리한다. 

>>> print('A1','B2')
A1 B2
>>> print('A1','B2',sep=',')
A1,B2
>>> print('aa',end='')
aa
>>> print('A1','B2',end='')
A1 B2

>>> a=['A','B']
>>> print(''.join(a))
AB
>>> print(' '.join(a))
A B

>>> idx = 1
>>> fruit = "Apple"
>>> print('{0}:{1}'.format(idx+1, fruit))
2:Apple
>>> print(f'{idx+1}:{fruit}')
2:Apple

 

| pass 

파이썬에서 pass는 널 연산 null Operation 으로 아무것도 하지 않는 기능. 

pass는 먼저 목업 mockup 인터페이스 부터 구현한 다음 추후 구현을 진행할 수 있게한다. 

class MyClass(object):
    def method_a(self):
    
    def method_b(self):
        print("Method B")

c = MyClass() 

#클래스 실행이 되지 않고, 인덴트 오류가 발생한다. 
#method_a 가가 아무런 처리를 하지 않았기 때문에 method_b에서 
#오류가 발생함.
class MyClass(object):
    def method_a(self):
        # 여기에 pass 추가
        pass
    def method_b(self):
        print("Method B")

c = MyClass()

 

| locals 

locals()는 로컬 심볼 테이블 딕셔너리를 가져오는 메소드. 업데이트 가능하다. 

예를 들어 로컬 스코프에 제한해 정보를 저회할 수 있기 때문에 클래스의 특정 메소드 내부에서나 함수 내부의 로컬 정보를 조회해 잘못 선언한 부분이 없는지 확인하는 용도로 확인 가능하다. 

변수명을 일일이 찾아낼 필요 없이 로컬 스코프에 정의된 모든 변수를 출력하기 때문에 편리하다. 

import pprint
pprint.pprint(locals())

'''출력:
{'__annotations__': {},
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': None,
 '__file__': 'C:/pythonProject/codinginterview/test.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x014FAF58>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'pprint': (생략)}

'''

 

코딩 스타일

구글 파이썬 스타일 가이드 google.github.io/styleguide/pyguide.html

 

styleguide

Style guides for Google-originated open-source projects

google.github.io

| 변수명과 주석 

항상 코드에 상세한 주석을 달아두는 편이 좋다. 

 

| 리스트 컴프리헨션

특유의 문법과 의미를 축약하여 나타내는 특징으로 매우 강력한 기능이나, 

지나치게 남발하게 되면 파이썬의 가독성을 떨어트리는 요인이 되기도 한다. 

 

| 구글 파이썬 스타일 가이드 

함수의 기본 값으로 가변 객체 Mutable Object 를 사용하지 않아야 한다. 

함수가 객체를 수정하면 (리스트에 아이템을 추가한다든지 ) 기본값이 변경되기 때문. 

따라서 기본값으로 []나 {}를 사용하는 것은 지양해야 한다. 

대신 불변 객체 Immutable 를 사용한다. None을 명시적으로 할당하는 것도 좋은 방법이다. 

No : def foo (a, b=[]) :
    ...
No : def foo (a, b : Mapping = {}) :
    ...

Yes : def foo (a, b=None) :
    if b is None :
        b=[]
Yes : def foo (a, b:Optional[Sequence] = None) :
    if b is None :
        b=[]

True,False를 판별할 때는 암시적 Implicit 인 방법을 사용하는 편이 간결하고 가독성이 높다. 

굳이 False임을 if foo!=[] : 같은 형태로 판별할 필요가 없다. if foo : 로 충분하다.

Yes: if not users :
    print('no users')
    
    if foo = 0:
        ...
    if i%10==0 :
        ...

No : if len(users)==0 :
    print('no users')
    
    if foo is not None and not foo : 
        ...
    if not i%10 : 
        ...