Python 스터디 - 01

오정배·2023년 9월 28일
0

Python 스터디

목록 보기
1/2

Python 스터디 하면서 했던 내용들을 복습하면서 어려웠던 것들을 정리해보려 합니다.


실수(float) 자료형

float의 특수값

inf

  • 양의 무한대를 나타내는 상수
    x = float("inf")

-inf

  • 음의 무한대를 나타내는 상수
    x = float("-inf")

부동소수점 문제

float 에서 소수점 연산의 경우 항상 정확하진 않다는 것을 알 수 있습니다.
바로 부동소수점 문제인데요.
예를들어 0.1과 0.2의 연산의 경우 당연히 0.3이 될 것이라고 생각했지만 0.30000000000000004가 출력이됩니다. 2진수로만 연산하는 컴퓨터의 특성상 0.1과 0.2를 2진수로 변환한후에 연산을 합니다.

print(0.1 + 0.2)
# 출력: 0.30000000000000004
.1 * 2 ==  .2 ---- 0
.2 * 2 ==  .4 ---- 0
.4 * 2 ==  .8 ---- 0
.8 * 2 == 1.6 ---- 0
.6 * 2 == 1.2 ---- 1
.2 * 2 ==  .4 ---- 1  => 여기서부터는 계속 반복됩니다.
.4 * 2 ==  .8 ---- 0
.8 * 2 == 1.6 ---- 0
.6 * 2 == 1.2 ---- 1
.2 * 2 ==  .4 ---- 1

0.1을 2진수로 연산하면 0.000110011...의 무한소수의 형태를 보입니다.

.2 * 2 ==  .4 ---- 0
.4 * 2 ==  .8 ---- 0
.8 * 2 == 1.6 ---- 0
.6 * 2 == 1.2 ---- 1
.2 * 2 ==  .4 ---- 1  => 여기서부터는 계속 반복됩니다.
.4 * 2 ==  .8 ---- 0
.8 * 2 == 1.6 ---- 0
.6 * 2 == 1.2 ---- 1
.2 * 2 ==  .4 ---- 1

0.2을 2진수로 연산하면 0.00110011...의 무한소수의 형태를 보입니다.

즉, 무한소수끼리의 연산으로 0.3의 값과 가깝지만 정확하게 연산이 되지 않는다는 것을 알 수 있습니다.

이를 해결하는 방법은 여러가지가 있지만 여기서 정리할 것은 두가지 입니다.

  • 첫번째는 decimal을 사용하여 정확한 연산을 하는 것.
import decimal

print(float(decimal.Decimal('0.1') + decimal.Decimal('0.2')))
# 출력: 0.3
  • 두번째는 10을 곱하고 연산을 한 후에 10을 나누는 것.
print((0.1*10 + 0.2*10)/10)
# 출력: 0.3

문자열 자료형

문자열 연산

문자열 슬라이싱

슬라이싱은 [시작지점:원하는 정지시점+1:간격]의 형태로 사용한다. 여기서 간격의 기본값은 1이고, 원하는 정지시점 +1을 한 이유는 원하는 정지시점 부분은 포함을 안하기 때문입니다.
예를 들어서 [1:10:1]일 경우에 정지시점에 10은 포함이 되지 않는다. 즉 1부터 9까지라는 말입니다.

s = '문자열 슬라이싱을 연습해보겠습니다.'
print(s[4:])
print(s[:])
print(s[::-1]) # 간격에 -1을 입력할 경우 역순
print(s[::2]) # 간격에 2를 입력할 경우 2칸씩 

# 출력 : 슬라이싱을 연습해보겠습니다.
# 출력 : 문자열 슬라이싱을 연습해보겠습니다.
# 출력 : .다니습겠보해습연 을싱이라슬 열자문
# 출력 : 문열슬이을연해겠니.

그리고 슬라이싱은 지정한 범위가 넘어가더라도 에러가 발생하지 않습니다.

s = '이렇게 지정한 범위가 넘어가도 에러가 안 생겨요'
print(s[5:30])

# 출력 : 정한 범위가 넘어가도 에러가 안 생겨요

문자열 메서드

문자열 메서드는 기억이 잘 안나는 것들만 정리

  • find()
    문자를 찾아 인덱스를 반환해준다.
    찾을 수 없을 경우 -1을 반환
  • index()
    문자를 찾아 인덱스를 반환해준다.
    찾을 수 없을 경우 error
s = "find와 index의 공통점과 차이점을 알아봅시다."
print(s.find("알아봅시다."))
print(s.index("알아봅시다."))

# 출력 : 23
# 출력 : 23

print(s.find("이것은 없는 문자열입니다."))
print(s.index("이것은 없는 문자열입니다."))

# 출력 : -1
# 출력 : ValueError: substring not found
  • count()
    지정한 문자 또는 숫자의 개수를 셀 때 사용한다.
s = "Let's talk about count."
print(s.count("t"))

# 출력 : 4 # t가 4개가 들어있다.
  • strip()
    양쪽의 공백, 지정한 문자열 등을 제거할 수 있습니다.
s1 = '                    strip이 무엇인지 알아봅시다.               '
print(s1.strip())

# 출력 : strip이 무엇인지 알아봅시다.

s2 = '      ,!!.           strip이 무엇인지 알아봅시다.        ,!!.  '
print(s1.strip(' ,!.'))

# 출력 : strip이 무엇인지 알아봅시다
  • replace()
    특정 문자열을 대체할 때 사용합니다.
s = 'replace에 대해서 알아봅시다.'
print(s.replace('대해서', '무슨 기능이 있을지'))

# 출력 : replace에 무슨 기능이 있을지 알아봅시다.

# 주의!!
# s 자체는 바뀌지 않습니다.
print(s)

# 출력 : replace에 대해서 알아봅시다.
  • join()
    리스트를 하나의 문자열로 합칩니다.
l = ['join에', '대해서', '알아봅시다.']
print('_'.join(l))

# 출력 : join에_대해서_알아봅시다.
  • isdigit()
    모든 문자열이 숫자면 True, 아니면 False
s1 = '1q2w3e4r'
s2 = '1!2@3#4$'
s3 = '1234'

print(s1.isdigit())
print(s2.isdigit())
print(s3.isdigit())

# 출력 : False
# 출력 : False
# 출력 : True
  • rjust()
    문자열을 지정한 길이로 맞추면서 왼쪽을 지정한 문자로 채웁니다.
s = '문자열입니다.'
print(s.rjust(14, '_'))

# 출력 : _______문자열입니다.
  • ljust()
    문자열을 지정한 길이로 맞추면서 오른쪽을 지정한 문자로 채웁니다.
s = '문자열입니다.'
print(s.ljust(14, '_'))

# 출력 : 문자열입니다._______
  • center()
    문자열을 지정한 길이로 맞추면서 양쪽을 지정한 문자로 채웁니다.
s = '문자열입니다.'
print(s.center(15, '_'))

# 출력 : ____문자열입니다.____
  • zfill()
    지정한 길이만큼 문자열의 앞쪽을 0으로 채웁니다.
s = "27"
use_zfill = s.zfill(5)
print(use_zfill)

# 출력 : 00027
  • translate()
    문자열 내의 특정 문자를 교체 또는 삭제할 때 사용합니다.
    • 문자 교체
    1. maketrans를 사용하여 변환 테이블 생성

      table = str.maketrans('rat', 'cat')
    2. translate메서드로 문자열 치환

      s = 'translate에 대해서 알아봅시다.'
      use_translate = s.translate(table)
      print(use_translate)
      
      # 출력 ; tcanslate에 대해서 알아봅시다.
    • 문자 삭제
    1. maketrans를 사용하여 변환 테이블 생성

      table = str.maketrans('', '', 'rat')
    2. translate메서드로 문자열 치환

      s = 'translate에 대해서 알아봅시다.'
      use_translate = s.translate(table)
      print(use_translate)
      
      # 출력 ; nsle에 대해서 알아봅시다.

연산자

산술연산

  • list와 tuple의 덧셈
    list와 tuple에서의 덧셈은 두객체를 연결함을 뜻합니다.
l1 = ['리', '스', '트', '의']
l2 = ['덧', '셈']

t1 = ('튜', '플', '의')
t2 = ('덧', '셈')

print(l1 + l2)
print(t1 + t2)

# 출력 : ['리', '스', '트', '의', '덧', '셈']
# 출력 : ('튜', '플', '의', '덧', '셈')
  • set의 뺼셈
    set에서의 뺄셈은 차집합을 의미합니다.
print({1, 2, 3, 4, 5, 6, 7, 8, 9} - {1, 3, 5, 7, 9})

# 출력 : {8, 2, 4, 6} -> 요소의 순서는 랜덤입니다!
  • 나눗셈(///의 차이) -> 개인적으로 자꾸 헷갈리는 부분
    / 연산자는 항상 float 형식으로 반환이 됩니다.
    // 연산자는 항상 int 형식으로 반환이 됩니다.
    여기서 //의 경우는 내림인데요, 음수에서의 내림과 양수에서의 내림이 헷갈릴 수 있습니다.
print(10 // 3)
# 출력 : 3 -> 3.333333....에서 내림하여 3으로 출력

print(-10 // 3)
# 출력 : -4 -> -3.333333....에서 내림하여 -4으로 출력
  • 음수의 거듭제곱
print(-3 ** 2)
# 9가 출력될 것 같지만 -9가 출력된다.
# 왜? -부호가 연산 후에 붙기 때문

논리연산

단락평가

  • and 연산자
    A and B 연산에서 A가 False면 B는 평가되지 않는다. 바로 False로 결정됨.
  • or 연산자
    A or B 연산에서 A가 True면 B는 평가되지 않는다. 바로 True로 결정됨.

우선순위

논리 연산자에서 우선순위는 not / and / or 순으로 되어있다.


시퀀스 자료형

리스트

  • 리스트는 항목의 변경이 가능합니다.
l = [1, 2, 3, 4, 5]
l[3] = 5000
print(l) # 출력 : [1, 2, 3, 5000, 5]
  • 문자열은 리스트와 달리 변경이 불가능.(immutable)
s = 'cantchange'
s[3] = 'p'
print(s) # TypeError: 'str' object does not support item assignment

다차원 리스트

  • 리스트 안에 리스트를 넣어서 만들 수 있습니다.
l = [[1, 2, 3], [4, 5], 6, 7, 8]
print(l[0][2]) # 출력 : 3

리스트 연산

덧셈과 곱셈

  • 리스트에서의 덧셈은 리스트의 연결을 의미합니다.
la = [1, 2, 3, 4, 5]
lb = [6, 7, 8, 9, 10]

print(la + lb) # 출력 : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • 리스트에서의 곱셈은 리스트의 반복을 의미합니다.
l = [1, 2, 3, 4, 5]

print(l * 2) # 출력 : [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

리스트 인덱싱

  • 리스트는 인덱스를 통해서 각 순서에 맞는 요소들을 선택할 수 있습니다.

리스트 슬라이싱

  • 리스트는 슬라이싱을 통해서 리스트의 일부분을 선택할 수 있습니다.
    ※ 중요! 슬라이싱은 범위를 넘어가도 에러가 나지 않습니다.
l = [1, 2, 3, 4, 5]

print(l[3:20]) # 출력 : [4, 5]

리스트의 구조 및 특징

메모리 구조

  • 리스트는 메모리에 원소들을 저장하는 것이 아닌 저장된 객체를 참조를 통해서 가리킵니다. 즉, 리스트는 객체를 저장하는 것이 아닌 객체의 주소를 저장하는 것이라고 볼 수 있습니다.
a = [3, 4, 5]
l = [1, 2, 'sss', a, 5]

print(id(1), id(l[0])) # 출력 : 2180830789872 2180830789872
print(id('sss'), id(l[2])) # 출력 : 2180835911152 2180835911152
print(id(a), id(l[3])) # 출력 : 2180835991616 2180835991616
print(id(a[2]), id(l[4])) # 출력 : 2180830790000 2180830790000

동일한 주소값(id)를 출력하는 모습을 볼 수 있습니다.

메모리 효율성과 성능

  • 리스트 메모리 구조의 장점은 다양한 자료형을 원소로 포함할 수 있고, 데이터의 추가 및 삭제가 상대적으로 효율적입니다.

  • 하지만 참조를 저장하기 위한 추가 메모리 공간이 필요하고, 데이터가 연속적인 메모리 공간에 저장되지 않아 캐시의 지역성이 떨어진다는 단점이 있습니다.

  • 리스트의 메모리 구조는 다양한 자료형과 동적인 크기 변경을 지원하기 위해 트레이드 오프로 일부 메모리 및 성능 효율성을 포기하고 있습니다.

    캐시의 지역성(cache locality)

    CPU와 가장 가까운 캐시를 캐시 메모리라 합니다.
    이때 캐시 메모리가 역할을 정상적으로 수행하기 위해서 CPU가 어떤 데이터를 원할 것인지를 예측할 수 있어야 합니다.
    CPU가 원하는 데이터가 캐시에 있을 확률을 뜻하는 Hit rate를 극대화 시키기위해 데이터 지역성을 사용하는데 이 지역성에는 대표적으로 시간적 지역성공간적 지역성 2가지가 있습니다.
    그 중 여기서 의미하는 지역성은 공간적 지역성입니다. 이는 기억장치 내에 인접하여 저장되어있는 데이터들이 연속적으로 접근될 가능성이 높아지는 것을 의미합니다.

    트레이드 오프(trade-off)

    어떤 것을 얻기 위해 다른 것을 포기하는 것.

접근시간

  • 인덱스를 통한 접근(시간복잡도: O(1))
  • 슬라이싱(시간복잡도: O(k)) # 여기서 k는 슬라이싱의 길이입니다.
  • 검색(시간복잡도: O(n))
  • 마지막 요소 접근(시간복잡도: O(1))

    시간복잡도

    자세한 사항은 추후 따로 정리하겠으며, 여기서는 Big-O 표기법에 해당하는 시간 복잡도만 간단하게 학습하여 정리하였음.
    Big-O 표기법의 종류
    1. O(1) - 일정한 복잡도(constant complexity)
      - 입력갑의 크기와 관계없이, 즉시 출력값을 얻어낼수 있다.

    2. O(n) - 선형 복잡도(linear complexity)
      - 입력값의 크기의 증가에 따라 시간도 같은 비율로 증가한다.

    3. O(log n) - 로그 복잡도(logarithmic complexity)
      - O(1) 다음으로 빠른 시간 복잡도이며, 매번 탐색을 할 경우 경우의 수가 절반씩 줄어드는 기법이다.

    4. O(n2) - 2차 복잡도(quadratic complexity)
      - 입력값의 크기의 증가에 따라 시간이 n의 제곱의 비율로 증가한다.

    5. O(2n) - 기하급수적 복잡도(exponential complexity)
      • 가장 느린 시간 복잡도이며 입력값의 증가에 따라 시간의 2의 n제곱의 비율로 증가한다.

리스트 메서드

  • append()
    리스트 끝에 값을 추가
l = [1, 2, 3, 4, 5]
l.append(100)
print(l) # 출력 : [1, 2, 3, 4, 5, 100]
  • clear()
    리스트 모든 항목 삭제
  • copy()
    리스트의 얕은 복사
    즉, 동일한 요소를 가진 다른 새로운 리스트를 생성합니다.
l = [1, 2, 3, 4, 5]
newl = l.copy()
newl[0] = 2727
print(l) # 출력 : [1, 2, 3, 4, 5]
print(newl) # 출력 : [2727, 2, 3, 4, 5]

여기서는 newl[1, 2, 3, 4, 5]를 가리키는 것이 아닌 같은 주소를 가진 요소를 갖고 있는 리스트를 복사하는 것입니다.

copy()를 하지 않을 경우

l = [1, 2, 3, 4, 5]
newl = l
newl[0] = 2727
print(l) # 출력 : [2727, 2, 3, 4, 5]
print(newl) # 출력 : [2727, 2, 3, 4, 5]

여기서는 newl[1, 2, 3, 4, 5]를 가리키기 때문에 newl[0]l[0]과 동일한 요소입니다.

  • 깊은복사
    copy 모듈을 사용하면 깊은 복사를 할 수 있습니다.

    import copy
    
    l1 = [10000]
    
    l2 = [27, l1]
    l3 = [87, l1]
    
    l4 = [l2, l3]
    l5 = copy.deepcopy(l4)
    l1[0] = 999
    
    print(l4) # 출력 : [[27, [999]], [87, [999]]]
    print(l5) # 출력 : [[27, [10000]], [87, [10000]]] # 깊은 복사

튜플(Tuple)

튜플은 변경 불가능(immutable)

  • 값이 변경되면 안 되는 경우 튜플을 사용하면 데이터의 안정성을 높일 수 있습니다.

튜플의 구조 및 특징

메모리 구조

  • 튜플의 메모리 구조는 리스트와 유사하다.
  • 주소의 값 자체를 저장하는 것이 아닌 값의 주소를 저장함으로 값을 가리킨다.

불변성

  • 리스트는 원소의 추가나 삭제를 고려하여 추가 메모리를 사용하지만, 튜플은 크기가 고정되어있기 때문에 추가적인 메모리를 필요로 하지 않습니다.
  • 튜플의 불변성은 데이터 변경을 방지해줍니다.

딕셔너리(Dict)

  • key는 중복되지 않습니다. 만약 중복된 key로 할당할 경우 마지막에 할당한 값만 유지합니다.
  • 튜플의 리스트로 딕셔너리를 생성하는 것이 가능합니다.
d = dict([('name', '이름의 value입니다.'),('age', '나이의 value입니다.')])
print(d)

# 출력 : {'name': '이름의 value입니다.', 'age': '나이의 value입니다.'}
  • 키워드 인자로 생성하는 것이 가능합니다.
d = dict(name = 'licat', age = 10)
print(d)

# 출력 : {'name': 'licat', 'age': 10}

메모리 구조 및 특징

  • 딕셔너리는 내부적으로 해시테이블을 사용하며 이는 데이터의 검색, 추가, 삭제를 시간복잡도 O(1)로 처리가 가능합니다.

딕셔너리의 메서드 (익숙한 메서드는 생략함.)

  • clear()
    딕셔너리의 모든 key-value 쌍을 삭제합니다.

  • fromkeys()
    시퀀스 자료형으로 딕셔너리를 생성할 수 있습니다.

    keys = ['요소1', '요소2', '요소3']
    value = None
    print(dict.fromkeys(keys, value))
    
    # 출력 : {'요소1': None, '요소2': None, '요소3': None}

    시퀀스 자료형으로 하나하나 요소를 value로 지정하는 것은 불가능합니다.

    keys = ['요소1', '요소2', '요소3']
    values = ['값1', '값2', '값3']
    print(dict.fromkeys(keys, values))
    
    # 출력 : {'요소1': ['값1', '값2', '값3'], '요소2': ['값1', '값2', '값3'], '요소3': ['값1', '값2', '값3']}
  • items()
    딕셔너리의 키와 값을 쌍으로 추출할 때 사용합니다.

    d = {'요소1': ['값1', '값2', '값3'], '요소2': ['값1', '값2', '값3'], '요소3': ['값1', '값2', '값3']}
    print(d.items())
    
    # 출력 : dict_items([('요소1', ['값1', '값2', '값3']), ('요소2', ['값1', '값2', '값3']), ('요소3', ['값1', '값2', '값3'])])
    # 리스트로 변환시
    
    print(list(d.items()))
    # 출력 : [('요소1', ['값1', '값2', '값3']), ('요소2', ['값1', '값2', '값3']), ('요소3', ['값1', '값2', '값3'])]
  • pop()
    주어진 key의 value를 반환하고 key-value 쌍을 삭제합니다.

    d = {'요소1': '값1', '요소2': '값2', '요소3': '값3'}
    popd = d.pop('요소1')
    print(popd)
    print(d)
    
    # 출력 : 값1
    # 출력 : {'요소2': '값2', '요소3': '값3'}
  • popitem()
    마지막 key-value 쌍을 반환하고 삭제합니다.

    d = {'요소1': '값1', '요소2': '값2', '요소3': '값3'}
    poplast = d.popitem()
    print(poplast)
    print(d)
    
    # 출력 : {'요소1': '값1'}
    # 출력 : {'요소2': '값2', '요소3': '값3'}
  • setdefault()
    주어진 key의 value를 반환합니다. key가 없을 경우 새로 지정합니다.
    key가 있을 경우 value가 수정되지 않습니다.

    d = {'요소1': '값1', '요소2': '값2', '요소3': '값3'}
    dd = d.setdefault({'요소4' : '값4'}
    print(dd)
    print(d)
    
    # 출력 : 값4
    # 출력 : {'요소1': '값1', '요소2': '값2', '요소3': '값3', '요소4': '값4'}
  • update()
    딕셔너리의 새로운 값을 추가할 때 사용합니다.

    d = {'요소1': '값1', '요소2': '값2', '요소3': '값3'}
    d.update({'요소4' : '값4'}
    print(d)
    
    # 출력 : {'요소1': '값1', '요소2': '값2', '요소3': '값3', '요소4': '값4'}

셋(Set)

Set 자료형은 중복을 허용하지 않으며, 순서가 없습니다.

집합의 연산

합집합, 교집합, 차집합의 연산

  • 합집합 ( | )
set1 = {2, 4, 6}
set2 = {3, 5, 7}
합집합 = set1 | set2
print(unionset)

# 출력 : {2, 3, 4, 5, 6, 7}
  • 교집합 ( & )
set1 = {2, 4, 6}
set2 = {3, 4, 5, 6, 7}
교집합 = set1 & set2
print(교집합)

# 출력 : {4, 6}
  • 차집합 ( - )
set1 = {2, 3, 4, 5, 6, 7}
set2 = {3, 5, 7}
차집합 = set1 - set2
print(차집합)

# 출력 : {2, 4, 6}

특정값이 들어있는지 확인

in 키워드 사용

set1 = {2, 3, 4, 5, 6, 7}

print(4 in set1)
print(9 in set1)

# 출력 : True
# 출력 : False

집합의 메서드

  • remove() 와 discard()
    remove는 지정한 요소를 삭제하지만, 지정한 요소가 없을 경우 KeyError가 발생합니다.
    하지만 discard의 경우 비슷한 기능을 하지만, 지정한 요소가 없다고 하더라도 에러가 발생하지 않습니다.
set1 = {2, 3, 4, 5, 6}
set1.remove(3)
print(set1) # 출력 : {2, 4, 5, 6}

set1.remove(1) # KeyError
set1 = {2, 3, 4, 5, 6}
set1.discard(3)
print(set1) # 출력 : {2, 4, 5, 6}

set1.discard(1) # 에러가 발생하지 않습니다.
  • difference(others) 와 difference_update(others)
    difference는 모든 others와의 차집합을 반환합니다.
    difference_update는 others와의 차집합으로 업데이트합니다.
set1 = {2, 3, 4, 5, 6, 7}
set2 = {3, 5, 7}
print(set1.difference(set2))

# 출력 : {2, 4, 6}
set1 = {2, 3, 4, 5, 6, 7}
set2 = {3, 5, 7}
set1.difference_update(set2)
print(set1)

# 출력 : {2, 4, 6}
  • intersection(others) 와 intersection_update(others)
    intersection은 모든 others와의 교집합을 반환합니다.
    intersection_update는 others와의 교집합으로 업데이트합니다.
set1 = {2, 3, 4, 5, 6, 7}
set2 = {3, 5, 9}
print(set1.intersection(set2))

# 출력 : {3, 5}
set1 = {2, 3, 4, 5, 6, 7}
set2 = {3, 5, 9}
set1.intersection_update(set2)
print(set1)

# 출력 : {3, 5}
profile
개발 관련 블로그 입문

0개의 댓글

관련 채용 정보