Python 기초 - 12.기초마무리&복습

5 분 소요

변수의 유효범위와 메모리

  • 지역변수: 함수 영역 안에서만 동작하는 변수 - local variable

    • 함수가 호출될 때 임시 저장 공간(메모리)에 할당되고 함수 실행이 끝나면 사라진다.
    • 따라서 다른 함수에서 같은 이름으로 변수를 사용해도 각각 다른 임시 저장 공간에 할당되고 독립적으로 동작한다.
  • 전역변수: 함수 영역 밖에서 생성한 변수로 코드 내 어디서나 사용 가능 - global variable

  • 저장공간에 따른

    유효범위(scope)

    • 이름 공간: 변수를 정의할 때 변수가 저장되는 공간
    • 유효범위(scope) : 코드 내에서 영향을 미치는 범위
    • 지역변수를 저장하는 이름 공간 = 지역 영역 (local scope)
    • 전역변수를 저장하는 이름 공간 = 전역 영역 (global scope)
    • 파이썬 자체에서 정의한 이름 공간 = 내장 영역 (built-in scope)
    • 변수 확인 순서 (scoping rule 또는 LGB rule) : 지역 → 전역 → 내장
      • 동일한 변수명을 사용할 경우 스코핑 룰에 따라 변수를 선택

단순 복제 vs. 얕은복사 (shallow copy) vs. 깊은복사 (deep copy)

  • 단순복제

    a = [1, 2, 3, 4]
    a = b
    print(b)  # OUT: [1, 2, 3, 4]
      
    b[0] = 100
    print(a)  # OUT: [100, 2, 3, 4]
    
    • 변수만 복제를 했기 때문에 a와 b가 바라보는 객체는 [1, 2, 3, 4]로 동일

    • a의 값을 바꾸면 b의 값도 바뀌고, b의 값을 바꿔도 a의 값도 바뀐다

    • 단, list 처럼 mutable 한 객체의 참조값을 바꾸는 경우에만 해당되고 숫자나 문자열 같은 immutable 객체인 경우에는 해당되지 않는다. (새로운 값을 할당하는 것이라고 보면 됨)

      a = 10
      b = a
      print(b)  # OUT: 10
          
      b = "abc"
      print(b)  # OUT: "abc"
      print(a)  # OUT: 10
      

      복잡하게 설명했지만, 그냥 문법상 당연히 b의 값을 새로 정의하는 것처럼 보인다.

  • 얕은복사

    import copy
      
    a = [1, [1, 2, 3]]
    b = copy.copy(a)    # shallow copy 발생
    print(b)    # OUT: [1, [1, 2, 3]]
    b[0] = 100
    print(b)    # OUT: [100, [1, 2, 3]]
    print(a)    # OUT: [1, [1, 2, 3]] - shallow copy 가 발생해 복사된 리스트는 별도의 객체이므로 item을 수정하면 복사본만 수정된다. (immutable 객체의 경우)
      
    c = copy.copy(a)
    c[1].append(4)    # 리스트의 두번째 item(내부리스트)에 4를 추가
    print(c)    # [1, [1, 2, 3, 4]] 출력
    print(a)    # [1, [1, 2, 3, 4]] 출력, a가 c와 똑같이 수정된 이유는 리스트의 item 내부의 객체는 동일한 객체이므로 mutable한 리스트를 수정할때는 둘다 값이 변경됨
    
    • a를 복사해서 객체 b와 객체 c를 별도로 만들었지만, 리스트 속의 리스트에 대해서는 새로운 리스트를 만든 것이 아니라 참조값을 복사해왔기 때문에 영향을 받음
  • 딥카피

    import copy
      
    a = [1, [1, 2, 3]]
    b = copy.deepcopy(a)    # deep copy 실행
    print(b)    # OUT: [1, [1, 2, 3]]
    b[0] = 100
    b[1].append(4)
    print(b)    # OUT: [100, [1, 2, 3, 4]]
    print(a)    # OUT: [1, [1, 2, 3]]
    
    • 리스트 속의 리스트까지 참조값을 복사하는 것이 아니라 복제된 값을 새로 할당시켰기 때문에 서로 영향을 받지 않는다.

람다(lambda) 함수

  • lambda : 함수를 생성할 때 사용하는 예약어로 def와 동일한 역할. 간단한 연산 함수를 한 줄로 표현할 때 사용

  • 표현방법

    lambda 파라미터1, 파라미터2, ... : 표현식
      
    )
    add = lambda a, b : a+b
    print(add(3,4))  # OUT: 7
      
    power = lambda x : x**2
    print(power(3))  # OUT: 9
      
    myFunc = lambda x, y, z : 2*x + 3*y + z
    print(myFunc(1,2,3))  # OUT: 11
    

인자 활용법

용어정리

  • 파라미터(parameter) = 매개변수. 함수를 정의할 때 나열되는 변수(variable)
  • 인자(argument) = 함수 호출시 전달되는 실제 값(value)

함수의 매개변수를 정의하고 인자를 요구할 때 다양한 방법으로 인자를 요구하여 전달받을 수 있다.

  • 위치인자 : 매개변수와 인자의 위치를 일치시켜 매칭하는 방법

    def func(a, b, c):
    		print(a, b, c)
      
    func(3, 2, 1)  # a=3, b=2, c=1.  OUT: 3 2 1
    
  • 키워드인자: 매개변수와 인자의 이름을 일치시켜서 매칭하는 방법. 인자 입력 순서가 바뀌어도 상관없음

    def func(a, b, c):
    		print(a, b, c)
      
    func(c=30, a=10, b=20)  # OUT: 10 20 30
    
  • 위치인자와 키워드 인자를 섞어서 사용해도 된다. 다만, 위치인자를 키워드인자보다 먼저 작성해야한다.

    def func(a, b, c, d, e):
    		print(a, b, c, d, e)
      
    func(5, 4, e=1, d=2, c=3)  # 가능. OUT: 5 4 3 2 1
      
    func(d=2, e=1, 3, 4, a=5)  # 불가능
    
  • 가변인자: 입력받을 **인자의 수를 제한하지 않고 튜플로 받아서 쓸 수 있다

    def func(*nums):
    		for i in nums:
    				print(i)
      
    func(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)  # OUT: 1 2 3 4 5 6 7 8 9 10
      
    def add(*x):
    		s = 0		
    		for i in x:
    				s += i
    		return s
      
    print(add(5, 10 ,15, 20, 25))  # OUT: 75
    
  • 매개변수 기본값 지정: 매개변수의 기본값을 지정해놓으면 인자를 보내지 않아도 기본값을 사용하여 함수를 실행한다.

    def func(a, b=5):
    		return a+b
      
    print(func(3))  # OUT: 8
    print(func(3, 7))  # OUT: 10
    

재귀함수

  • 자신을 호출하여 사용하는 함수

  • 반복동작을 짧게 구현할 수 있는 장점, 그러나 메모리를 많이 차지하고 스택오버플로우가 발생할 수 있다.

    1. 함수를 호출 시 함수의 매개변수, 지역변수, 리턴 값, 그리고 함수 종료 후 돌아가는 위치를 스택 메모리에 저장한다.
    2. 재귀함수를 사용시 함수를 반복하여 호출. 1번 과정을 되풀이한다.
    3. 호출하는 횟수가 많아질 수록 사용하는 스택 메모리가 커지고 결국 스택 오버플로우로 이어진다.

    ⇒ 대부분 재귀함수는 루프로 대체가 가능하기 때문에 루프로 대체하는 것이 좋다.

    • 재귀함수의 가장 대표적인 사용 예인 팩토리얼 계산:
    def factorial(num):
        if num==1:
            return 1
        else:
            return num * factorial(num-1)
    

함수객체(functor)

변수에 함수를 담아서 활용, 함수 속의 함수. (클래스 예제에서 이미 많이 진행시켜 본 구조다)

  • 핸들러: 이벤트가 발생했을 때 그 처리를 담당하는 실행 함수
def onEvent(f):
		print('이벤트 등록')
		f()  # onEvent 함수가 호출되면 f() 함수를 실행한다

def handler():  # onEvent 함수 호출시 실질적으로 실행될 함수
		print('3의 배수')

def main():
		for i in range(1, 100):
				print(i)
				if i % 3 == 0:  # 3으로 나눠서 나머지가 없을 때
						onEvent(handler)  #onEvent 이벤트 발생, handler 함수를 실행하여 '3의 배수'를 출력한다.
  • 룩업테이블 (순람표, LUT - Look Up Table) : 원하는 값으로 쉽게 도달하게 해주는 좌표값
#룩업테이블(리스트)
def 밥먹기():
    print('피카츄 밥먹음')

def 놀기():
    print('피카츄 놀음')

def 잠자기():
    print('피카츄 잠')

def 운동하기():
    print('피카츄 운동함')

def main():
    funcs = [밥먹기, 놀기, 잠자기, 운동하기]
    menu = int(input('1.밥 2.놀 3.잠 4.운동'))
    funcs[menu-1]()

클래스에서 사용하는 함수의 종류

인스턴스 메서드:

class Test:
		def instanceMethod(self):
				print('인스턴스 메서드')

x = Test()
x.instanceMethod()  # OUT: '인스턴스 메서드'

각 객체에서 개별적으로 동작하는 함수를 만들 때 사용. 첫 인자로 self를 필요로 하는, 지금까지의 예제들에서 가장 보편적으로 정의하고 사용한 메서드.

정적 멤버 변수 & 정적 메서드:

class Test:
		a = 0  # 클래스 변수/정적 멤버 변수
		@staticmethod  # 데코레이터, 정적 메서드임을 표시
		def staticMethod():
				if a < 0 :
						print('음수')
				elif a > 0 :
						print('양수')	
  • 정적 멤버 변수: 정적메모리(static)에 저장. 프로그램이 시작할 때부터 종료할 때까지 메모리가 유지되며, 이 클래스로 만든 모든 객체가 공용으로 사용
  • 정적 메서드: 클래스와 관련이 있어서 클래스 안에 두기는 하지만 클래스나 객체와 무관하게 독립적으로 동작하는 함수를 만들고 싶을 경우 이용.
    • self를 사용하지 않는다. 바로 클래스명을 사용하여 호출 가능 (Test.statciMethod())
    • 정적 메서드 안에서는 인스턴스 메서드나 인스턴스 변수에 접근할 수 없다

클래스 메서드

class Test:
		@classmethod
		def classMethod(cls, 인자, 인자, ...):
  • 클래스 변수를 사용하기 위한 함수. 함수 정의 시 첫 번째 인자로 반드시 클래스를 넘겨받는 cls가 필요. self를 사용하지 않아 객체 생성없이 바로 클래스명을 사용하여 호출 가능. (Test.classMethod(cls))

댓글남기기