Upstage AI LAB 부트캠프 5기/실시간 공부내용 복습

[2024.10.01] 파이썬 프로그래밍 이해하기

김 도경 2024. 10. 2. 17:07

* 강의를 듣고 필기한 내용일 이후에 따로 정리한 내용입니다.

* https://glowdp.tistory.com/12 에서 이어지는 게시물 입니다.

 

(4) 단어 갯수 구하기

pep8 = """This document gives coding conventions for the Python code comprising the standard library in the main Python distribution. Please see the companion informational PEP describing style guidelines for the C code in the C implementation of Python.
This document and PEP 257 (Docstring Conventions) were adapted from Guido’s original Python Style Guide essay, with some additions from Barry’s style guide [2].
This style guide evolves over time as additional conventions are identified and past conventions are rendered obsolete by changes in the language itself.
Many projects have their own coding style guidelines. In the event of any conflicts, such project-specific guides take precedence for that project.
"""

# 각 단어별로 몇 번씩 등장하는지 모두 출력
# 1)
word_count = {}  # key : value = word : count, 빈 딕셔너리, 각 단어를 키로 하고 그 단어가 등장하는 횟수를 값으로 저장
for word in pep8.split():   # pep8.split()는 pep8 문자열을 공백(스페이스, 줄바꿈 등)을 기준으로 나누어 단어 리스트를 반환
    if word in word_count:  # word 값이 word_count의 key에 존재하는지 여부 True/False
        word_count[word] = word_count[word] + 1  # 이미 존재하면 word_count[word] 값을 1 증가
    else:
        word_count[word] = 1    # 존재하지 않으면 word_count[word] = 1로 설정하여 새 단어를 추가하고, 해당 단어의 초기 카운트를 1로 설정

word_count

#2)
word_count = {} # key : value = word : count        # word가 word_count에 존재하면 그 값을 반환하고, 존재하지 않으면 기본값인 0을 반
for word in pep8.split():                             #for 루프를 사용해 각 단어를 순회
    word_count[word] = word_count.get(word, 0) +1    #+ 1을  더하여 word_count에 해당 단어의 카운트를 추가하거나 갱신

#3)
from collections import Counter     #collections 모듈의 Counter 클래스를 사용하여 단어별로 등장 횟수를 자동으로 셈

Counter(pep8.split())         #pep8.split()로 얻은 단어 리스트를 Counter에 전달하면, 각 단어의 등장 횟수를 카운트한 딕셔너리와 유사한 객체가 반환

1)기초적인 if-else 구문을 사용하여 명확하게 보여줌.
2) get() 메서드를 활용하여 코드를 간결하게 만듬
3) Counter를 사용하여 가장 효율적이고 간단한 방식으로 해결

- https://peps.python.org/pep-0008/ : 파이썬 코드 스타일 가이드, 일관성 있게 코드를 작성할 수 있도록 도움

- https://peps.python.org/pep-0257/ : Docstring 컨벤션을 정의한 문서. 함수, 클래스, 모듈 등에서 해당 코드의 설명을 담는 문자열로, 파이썬의 문서화 기능 중 하나

- https://peps.python.org/pep-0020/ : 파이썬의 설계 철학을 담은 20가지의 가이드라인, 파이썬의 창시자인 Guido van Rossum이 제안한 철학적 원칙들을 간결하게 정리한 것 (파이썬 코드를 작성할 때 따를 수 있는 중요한 규칙)


Function이란?
수학적인 의미의 함수와 개념은 비슷하지만 역할이 다르다.
  • input이 들어와서 output이 정해진 규칙에 따라 나온다는 개념은 같지만, 프로그램에서의 하나의 함수는 하나의 기능
  • 정확하게 함수는 특정 기능을 구현한 코드 묶음이다.
  • def 함수이름(param1, param2, ... ):
      <statement1>
      <statement2>
    return
  • 함수를 쓰는 이유는 재사용성 때문 : Reusability라고 하며, 똑같은 구조의 코드가 반복되는 것을 피하기 위해 사용됨.
    - 똑같은 구조의 코드는 보통 한 가지의 기능 단위로 묶이게 되며, 이 기능 단위를 코드로 묶어서 함수로 만듬.

 

  • built- in function : 별도의 모듈이나 라이브러리를 임포트하지 않고도 바로 사용할 수 있는 함수
print()
input()
len()
type()
...
  • user-defined function
    - 직접 만드는 function
def foo()                 # function definition : 함수가 입력으로 뭘 받을지 정의
    '''
    '''
    return
    
foo()                     #function call
def foo(a, b):              # function definition : 함수가 입력으로 뭘 받을지 정의
    c = a + b
    return c             # return에서 마무리, 함수는 만들어지고 call할 때, 함수가 어떤 것을 계산하냐?

foo(3 ,5)                   #function call

-> 8이 출력

- def: 함수 정의를 시작하는 키워드. 파이썬에게 새로운 함수를 정의한다고 알려줌.
- foo: 함수의 이름 : 함수는 이름으로 호출될 수 있음. 여기서는 foo라는 이름을 사용했지만, 의미 있는 이름을 사용하는 게 좋음
- a, b: 함수의 매개변수(parameters). 함수가 호출될 때 함수가 사용할 입력값을 나타냄. 여기서는 두 개의 매개변수를 받아들이도록 정의됨. 함수가 호출되면, a와 b는 전달된 값(인자, arguments)을 저장. 2개의 변수를 받아서 더해주는 값

     - 매개변수(parameters): 함수 정의에서 사용되는 변수들로, 함수가 외부로부터 입력을 받기 위해 사용. (a, b)
     - 인자(arguments): 함수를 호출할 때 전달하는 실제 값들. 예를 들어, foo(3, 5)에서 3과 5는 인자.

- body of the function : c = a + b: 함수는 a와 b를 더한 값을 c에 저장. 이 부분이 함수의 실제 동작을 정의

- return: 함수가 값을 반환하는 부분. 함수는 이 반환 값(return value)을 통해 함수 외부로 결과를 전달.
- c: 이 함수는 a와 b의 합을 저장한 c를 반환. 반환된 값은 함수 호출이 끝난 후 외부에서 사용할 수 있음

 

- ipython kernel : Jupyter Notebook 또는 다른 환경에서 코드를 실행할 때, IPython Kernel은 파이썬 코드를 받아 실행하고 그 결과를 사용자에게 반환

    - ipynb = interactive python notebook

- conda-forge

- PyPI

- repository

 

- 오픈 소스 라이브러리 ( open-source library )

    - 누군가가 만들어낸 코드를 공유하는 것 : 다른 언어도 있지만, 파이썬이 압도적으로 많음.

 

- user-defined function 연습하기

1) 나눗셈 연산 함수 만들기

def div(a, b):
    c = a/b
    return c

div(3, 4)
div(5, 2.5)
def div(a, b):
    if b == 0:
        print("0으로 나누시면 안됩니다!")
    else:
        return a/b

div(3, 4)
div(5, 2.5)

 

- 0으로 나누면 에러가 뜨기 때문에, 그걸 방지하기위해서 if문을 추가함.

def div(a : float, b: float) -> float:
    if a == 0 or b == 0:
        print("입력값 a와 b는 0이 될 수 없습니다. 다시 입력 부탁합니다.")        
    else:
        return a/b

div(3, 4)
div(5, 2.5)
div(8,0)

 

- a와 b에는 float(실수)만 들어감

def div(a,b):
# 예외처리(exception handling)-> 코드에서 에러가 발생했을때, 에러가 발생한 상황을 다른 코드로 처리하는 방식
    try:
        return a/b
    except:
        print("0으로 나누시면 안됩니다!")

print(div(8, 0))

 

- print를 하면 div(8,0)에 None(아무것도 없다는 의미)가 찍힘

 

  • 컴파일러 기반 언어 VS 인터프리터 기반 언어
     컴파일러 기반 : C, C++, Java
         - 컴파일을 할 때 미리 오류를 찾을 수 있음
         - 실제 실행시 발생 에러가 적음 : 빠른 속도, 코드 최적화 가능
         - 컴파일 과정이 필요해 코드 수정후 재컴파일이 반드시 필요, 플랫폼 의존적임
     인터 프리터 기반 언어 : Python, JavaScript, Ruby
         - 실행 전까지는 오류를 찾을 수 없음
         - 코드 수정후 바로 실행가능, 실행 환경 유연
         - 실행 속도가 느림, 코드 최적화 제한 : 실제 서비스를 개발하기 힘듬
  • GIL(Global Interpreter Lock) : 멀티스레딩 환경에서 오직 하나의 스레드만 Python 바이트코드를 실행할 수 있도록 제어하는 메커니즘 , Python에서 많이 언급되는 개념
        - Python의 메모리 관리 시스템이 스레드 간의 경합으로부터 메모리 안정성을 확보하기 위해 만들어짐.
        - 멀티 스레스에 한계가 있음 : python은 GIL 때문에 multithreading이 안됨
        - 구현에 제한이 됨.
        - IO 바운드 작업에 적합함.

 

  • 요즘 추세
    C++ -> Rust        : 메모리/동시성 처리 안전성, C++에서 많이 개선, 생산성 향상, 성능이 더 좋음
    Java -> Golang, Kotlin    : Java의 단점 보완, 단순/간결, 동시성 처리, 컴파일 속도가 빠름, 메모리 관리 가능 등 이유
    * 하지만 여전한 Top 5는 C++, C, Java, 파이썬, JavaScript
  • EDA(Exploratory Data Analysis)란?
    - 탐색적 데이터 분석(EDA, Exploratory Data Analysis)는 데이터 세트를 분석하고 그 특성을 시각적으로 또는 통계적으로 요약하여 데이터의 주요 패턴, 이상치, 분포, 상관관계 등을 파악하는 단계.
    - EDA는 데이터 분석 프로젝트나 머신러닝 모델링의 첫 번째 단계로, 데이터에 대한 이해를 높이고 이후의 데이터 전처리 및 모델링 과정을 효율적으로 진행하는 데 필수적인 과정
함수 정의의 다양한 형태

 

  • 가장 흔하게 사용 되는 경우 : 함수 parameter와 return이 모두 존재하는 경우.
def x(a, b):
    return a + b
  • 함수 parameter는 없고 return이 존재하는 경우.
def x():
    return "Hi"
  • 함수 parameter는 있는데 return이 없는 경우.
def x(a):
    a = a + 1
  • 함수 parameter도 없고 return도 없는 경우
def x():
    print("Hello World")
    
x()

 

 

  • 만약에 함수의 입력 parameter의 개수를 모를 때
def add_many(*args):
    # 파라미터로 입력받은 모든 숫자를 더해서 return 해주는 함수.
    total = 0  # intializion (초기화)
    for arg in args:  # args는 여러 개의 인자를 받아 튜플로 변환됩니다.
        total += arg  # total에 각 값을 더해줍니다.(= total = total + arg)
    
    return total

# 테스트
print(add_many(1, 2, 3))    # 6
print(add_many(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))  # 55

- *(asterisk)를 앞에 붙이는 것으로 여러개의 parameter를 받아서 tuple로 변환하여 준다.  

- total = total + arg → total += arg (더 간결한 방식)

 

  • 파라미터에 대해서
    - 함수에서 사용되는 변수들에게는 효력 범위와 수명이 있음

    Q. 만약에 함수의 파라미터 변수 이름과, 함수를 호출하는 argument의 이름이 같은 경우에 어떻게 될까?
        - 파이썬에서는 함수의 파라미터로, int/float/str/tuple(immutable data type)이 넘어가는 경우에는 내부에서 해당 변수를 수정해도 바뀌지 않음
        - 파이썬에서는 함수의 파라미터로, list/dict/set(mutable data type)이 넘어가는 경우에는 내부에서 해당 변수가 바뀜.
           : 반드시 암기하기
# global variable(전역함수)는 함수 외부에서 정의된 변수.

name = "Kim"
print(f"1. {name}")

def change_name(name):     # 함수 정의는 선언(declaration)만 한다.
    # 함수 내부에서 선언하는 변수들은 모두 "local variable(지역 변수)"라고 한다.
    print(f"2. {name}") 
    name = 'Lee'
    print(f"3. {name}")
    return name            # - 4는 나오지 않음 : 함수 내부 코드는 무조건 "return:을 실행하면 종료된다.
                            #함수 내부 코드는 무조건 "return"을 실행하면 종료됨 : local variable은 return과 함께 메모리에서 삭제
    print(f"4. {name}")        

print(f"5. {name}")
change_name(name)        #function call을 할 때, 함수 내부 코드를 실행
print(f"6. {name}")
change_name(name)
print(f"7. {name}")

 

1. Kim
5. Kim
2. Kim
3. Lee
6. Kim
2. Kim
3. Lee
7. Kim

- 4는 나오지 않음 : 함수 내부 코드는 무조건 "return"을 실행하면 종료된다.

- 순서 이해가 중요함 : 위에서 아래로 실행함 : 1->(5->2->3)->(6->2->3)->7

      - 1번 출력 : 가장 먼저 출력

      - 함수 정의는 선언(declaration)만 하고, 5번 출력으로 넘어감

      - 첫번째 함수 호출 -> 함수 내부로 진입 : 2,3번출력

      - 함수 종료 후에 6번 출력

      - 두번째 함수 호출 -> 함수 내부 재 진입 : 2,3번 출력

      - 마지막 전역 변수 7번 출력

  • Global Variable (전역 변수)
     - 전역 변수는 프로그램 전체에서 접근할 수 있는 변수
     - 프로그램의 어느 곳에서나 접근할 수 있으며, 함수 안팎에서 모두 사용
     - 종료시때까지는 언제든 있고, 메인 메모리(RAM)에 있음
     - 강제로 날리는 방법 1. rebooting , 2. del 명령어
del name

 

 

  • variable의 scope란, 코드 내에서 해당 변수에 접근할 수 있는 범위.
    - global variable의 scope는 코드 파일 전체,
    - local variable의 scope는 함수 내부. (UnboundLocalError= local variable로 선언을 안했단 뜻.)
    - 코드, 함수 내에서 특정 변수를 참조하려 할때, 해당 변수가 local variable에 선언된 것이 없으면 parameter를 먼저 체크할 뒤,  parameter로도 넘어오지 않았으면, global variable을 체크.

    # (ipynb 파일 한정으로)
def change_list(L):
    print(f"2. {L}")
    L[0] = 4
    L.pop()                    # 리스트 L의 마지막 원소 제거.
    print(f"3. {L}")

L = [1, 2, 3]
print(f"1. {L}")
change_list(L)                 # 리스트가 함수의 파라미터로 넘어가면, 내부에서 수정된게 다 "원본"에 반영됩니다.
print(f"4. {L}")

 

1. [1, 2, 3]
2. [1, 2, 3]
3. [4, 2] 
4. [4, 2]


- code block : 코드의 여러 줄을 하나의 그룹으로 묶은 것 / 들여쓰기로 구분
    - 보통 특정한 조건이나 함수 내에서 실행될 코드들을 모아서 묶어두는 구조
    - 함수, 조건문, 반복문 등에서 사용
    - 코드 블록 안에 있는 코드는 그 문맥에서 함께 실행됨

def say_hello():  # 함수 정의
    print("Hello!")  # 이 부분이 코드 블록
    
if 5 > 3:
    print("5 is greater than 3")  # 코드 블록

 

  • Lambda 함수(Lambda Expression) 사용
    - in line 함수
    - 굉장히 간단한 함수가 있는 경우, 한 줄짜리 함수로 간편하게 사용
    - 이런 함수를 Lambda 함수라고 하며, lambda 함수와 반복문을 통해 함수의 정의없이 다양한 프로그래밍이 가능
def add(a, b):
    return a+b
    
print(add(3, 5))

# lambda function, lambda expression, inline function --> function을 parameter passing해야하는 경우에 많이 사용함.
# lambda 함수로 바꾸면?

f = lambda a,b : a+b
f(3,5)
  • 중앙값 찾아서 return하는 함수 작성
    - sorted 함수와 copy로 하기

def find_median(L: list[int|float]) -> float:
    '''
    정수 또는 실수로 구성된 원소를 가지는 리스트에서 중앙값을 찾아서 return하는 함수.
    '''
    # L.sort() # 파라미터로 넘어온 원본 리스트가 정렬됨.
    
    # 1) (deepcopy)
    L2 = L           # shallow copy (L2와 L이란 실체는 아예 같은 것, 이름만 다른 것.)
    L = L.copy()     # deep copy (L2랑 L이랑 실체로도 다른 것.)
    L.sort()         # (L은 local variable)

    #2) sorted()
    L = sorted(L)
    
    if len(L) % 2 == 1:  # 원소가 홀수 개
        med_idx = len(L) // 2
        median = L[med_idx]
    else:  # 원소가 짝수 개
        med_idx = len(L) // 2
        median = (L[med_idx-1] + L[med_idx]) / 2

    return median


# Test Case 1.
L = [1, 2, 3, 4, 5]
print(find_median(L))

# Test Case 2.
L2 = [1, 4, 8, 10, 11, 14]
print(find_median(L2))

# Test Case 3.
L3 = [100, -11, 3, 3.5, 6, 7.7, 7]
print(find_median(L3))

* 마지막 문제 다시 풀어보기

* fuction 무조건 복습하기!