앞서 값을 LEGB 규칙에 대해 살펴보았다. 어떤 값을 지정하기 위해서는 확인하는 순서가 있는데 local 영역, enclosed 영역, global 영역, built in 영역 순서대로 값을 확인하는 규칙이었다.
그런데 어떤 값에 대해서 영역을 강제로 변경해서 사용자가 임의로 값의 영역을 지정할 수 있다. 이럴 때 사용하는 키워드가 global과 nonlocal 키워드이다.
a=1
def x():
def y():
a = a+1 # global 변수 = enclosed 변수 이므로 UnboundLocalError 발생
return a
return y
x()()
위 코드의 경우 UnboundLocalError가 발생한다. 현재 a는 global 영역에 정의되어있는데, a를 호출하는 a+1는 enclosed 영역이기 때문에 둘 사이에 미스매치가 일어난 것이다. 이때 아래와 같이 global 키워드를 사용해 코드를 고칠 수 있다.
# global 키워드 사용하기
a=1
def x():
def y():
global a
a = a+1
return a
return y
x()()
아래 코드도 확인해보자
def x():
a=3 # local 변수
def y():
a = a+1 # local 변수 = enclosed 변수 이므로 UnboundLocalError 발생
return a
return y
x()()
마찬가지로 UnboundLocalError가 발생한다. 이유는 a는 local 영역에 정의된 변수인데 a+1로 a를 호출하는 영역은 enclosed 영역이기 때문이다. 위 코드를 아래와 같이 nonlocal 키워드를 사용해 고치면 에러 없이 사용 가능하다.
def x():
a=3 # local 변수
def y():
nonlocal a
a = a+1
return a
return y
x()()
그런데 global과 nonlocal 키워드는 값을 지정하는데에 편법처럼 사용되는 것이기 때문에 자주 사용하지 않는 것이 낫다.
파이썬에서는 callable 개념이 매우 중요하다. callable을 간단하게 말하자면 괄호를 붙일 수 있는 것들이고 함수, method, class, generator가 있다. 앞서 3일차에서 함수와 generator에 대해 살펴보았다. 오늘은 class와 method에 대해 조금 더 깊게 알아보자.
class는 변수나 메소드를 사용하는 주체가 누구인지에 따라서 변수와 method가 구분된다.
class A:
x = 1 # class 변수
def t(self): # instance mothod
self.y = 2 # instance 변수
return self.y
a = A()
b = A()
# 호출하는 주체가 class - class 변수, class method 호출 가능
A.x # class변수는 class에서 접근 가능하다
A.y # instance 변수는 class에서 접근하지 못한다 -> 에러 발생
# 호출하는 주체가 객체 - instance 변수, instance method 호출 가능
# class 변수와 class method도 호출이 가능하긴 하지만, 객체를 달리 해도 공유된 값을 보유
a.x, b.x # 객체를 따로 만들어도 class 변수는 class에 종속된 변수이기 때문에 값이 같다
a.t() # 객체는 instance method에 접근이 가능하다.
python에서는 vars()
함수가 있다. vars()
함수는 어떤 객체가 가지는 instance 변수를 확인할 수 있는 변수이다. 이를 통해 instance method 실행 전후로 사용 가능한 instance 변수가 어떻게 변하는지 확인해보자.(dir()
은 이미 설명했으므로 생략하겠다.)
a = A()
vars(a), dir(a)
# 위 코드 실행 후 아래 코드 실행
a.t()
vars(a), dir(a)
a.t()
를 실행히기 전에는 y가 vars(a)
결과에 포함되지 않았다가, a.t()
를 실행한 후에 y가 포함된 것을 확인할 수 있다. 이를 통해 instance 변수는 instance method가 실행된 뒤에 생성된다는 것을 알 수 있다.
그런데 python의 문제는 너무 유연하게 사용이 가능하다 보니 객체를 생성한 뒤에도 instance 변수를 임의로 생성하거나 삭제하는 것이 가능하다는 점이다.
# 임의의 instance 변수를 생성하기
>>> a.aaaaaaaa = 'sun'
>>> vars(a)
{'y': 2, 'aaaaaaaa': 'sun'}
>>> del a.aaaaaaaa
>>> vars(a)
{'y': 2}
이처럼 임의로 instance 변수에 대하여 생성과 삭제가 가능하기 때문에 해킹의 위험이 있다.
함수에서 값을 지정하는 순서로 LEGB 규칙에 대해 이야기했다. 이와 비슷하게 class에서도 값을 지정하는 순서가 정해져있는데, class에서는 instance 변수 → class 변수 순으로 지정된다.
class B:
bb = 1
def c(self):
self.c=1
간단하게 class B를 구현해보았다. callable은 괄호를 붙일 수 있는 것이라고 했다. 괄호를 붙여 identifier를 지정하면 그 때 instance 혹은 객체가 된다. class B를 사용해 객체 두 개를 만들어보자.
xx = B()
yy = B()
instance 변수는 객체마다 가지는 고유한 값이다. 따라서 vars(xx)
와 vars(yy)
의 결과가 다르다.
>>> xx.a = 1 # xx에 instance 변수 추가
>>> vars(xx), vars(yy)
({'a': 1}, {})
xx에만 instance변수 a를 추가했기 때문에 vars(yy)
에는 a가 없는 것을 볼 수 있다. 그런데 한 가지 의문이 드는 것이 있다. 아래 코드를 보자.
>>> vars(xx)
{'a': 1}
>>> dir(xx)[-5:]
['__subclasshook__', '__weakref__', 'a', 'bb', 'c']
xx에는 bb라는 instance 변수가 없는데 왜 dir()
에는 나올까? 이것은 바로 class에서 값을 지정하는 순서 때문이다. 앞서 설명한 대로 객체가 사용가능한 변수는 instance 변수를 먼저 파악하고 없으면 class 변수를 할당한다. 따라서 dir()
의 결과에서 보이는 bb는 class 변수이고 vars()
에는 보이지 않는 것이다.(vars()
는 instance 변수만 보이기 때문)
>>> xx.bb = 2 # xx에 bb라는 이름의 instance 변수를 생성한다.(class 변수 bb는 덮어씌워짐)
>>> xx.bb, yy.bb
(2, 1)
객체를 생성한 뒤에 instance를 추가할 수 있듯 class를 생성한 뒤에도 class 변수를 추가할 수 있다.
>>> B.aaa = 'jw'
>>> xx.aaa, yy.aaa
('jw', 'jw')
xx와 yy에는 따로 aaa라는 변수를 추가하지 않았지만, aaa는 class 변수로 추가된 것이기 때문에 xx와 yy에서도 class 변수로서 접근이 가능하다.
method는 isntance method, class method, static method로 3종류이다. 이 중 객체가 사용하는 instance는 위에서 설명했고, class가 점근하는 class method와 name space의 역할로써 class를 사용하여 만드는 static method에 대해 배워보자.(static method를 이해하기 위해서는 name space를 이해해야 한다.)
class에서 접근이 가능한 변수는 class 변수이듯 class에서 접근이 가능한 method가 class method이다. 해당 클래스로 정의된 모든 객체가 동일하게 갖는 동작을 class method로 생성해 모든 변수가 접근해 사용 가능하게 만든다. class method를 정의하기 위해서는 @classmethod
데코레이터를 사용한다.
class C :
x = 1
@classmethod
def t(cls): # 관례상 class method는 cls라고 한다.(인스턴스 메소드는 self인 것처럼)
cls.y=1
print('class')
cls는 instance method에서 self를 사용하는 것처럼 관례상 사용한다. 아래에서 추가로 설명하겠지만 cls는 class 객체 자기 자신을 나타낸다.
method의 마지막 종류인 static method를 이해하기 위햇는 name space의 개념을 이해해야한다.
어떤 모듈을 import 한다는 것은 그 모듈을 정의한 .py 파일을 읽어온다는 의미이다. import한 .py 파일에서 정의한 변수, 함수, class를 사용할 수 있다.
import random
dir(random)
random 모듈 안에 있는 randint()
methodm와 동일한 이름의 함수를 생성해보 실행해보자.
def randint():
return 'name space sample'
>>> randint(), random.randint(1, 3)
('name space sample', 2)
결과는 놀랍게도 내가 만든 randint 함수와 random 모듈의 randint 둘 다 사용이 가능하다는 것이다. 어떻게 가능할까? 바로 name space가 다르기 때문이다. 그래서 random.
으로 name space를 정의한 것이다. (내가 생성한 randint 함수의 name space는 __main__
이다. 이는 현재 파일에서 정의하고 현재 파일에서 실행한다는 뜻으로 default로 붙는 name space임)
그런데 모듈을 import 할 때 from 절을 같이 쓰는 경우도 있다. 이 때 from은 import 할 method의 name space를 현재 sname space인 __main__
으로 가져온다는 의미이다. 따라서 내가 만든 randint는 random모듈의 randint에 덮어씌워지게 된다.(name space가 같아졌으므로)
>>> from random import randint
>>> randint(1, 3)
2
from의 한 가지 예외 사항으로는, method 이름이 언더바( _ )로 시작하는 method는 가져오지 않는다. 따라서 관례상 _ 로 시작하는 method는 그 파일 내부에서만 사용한다는 뜻이다.
static method는 class를 name space 역할로 사용하는 방법이다. 앞서 봤듯이 함수의 가장 큰 단점은 동일한 이름으로는 한 개밖에 조재하지 못한다는 것이고, 그 때문에 name space라는 개념이 나왔다. 이를 class로 확장한 방법이 static method이다.
static method를 선언하기 위해서는 @staticmethod
라는 데코레이터를 사용하면 되고, 다른 method와는 달리 self나 cls가 들어가지 않는다. 그 이유는 static method는 단순히 name space를 달리한 함수에 지나지 않기 때문이다.
class A :
@staticmethod # class는 name space 용도로만 사용하는 것이기 때문에 self나 cls와 같은 제약조건이 없다!
def tt():
print('tt')
A.tt()
비어있는 class 하나를 만들어보고 dir()
을 확인해보자.
class A:
pass
dir(A)
아무 동작도 지정하지 않았는데 dir()
결과로 무엇인가 많이 나온다. 이것들은 무엇일까?
사실 우리 눈에 보이지는 않지만, class를 정의할 때 자동으로 붙는 것이 있다. 위 코드는 사실 아래 내용이 생략된 것이었다.
class A(object, metaclass=type):
pass
pyhon의 모든 class는 object라는 상위 클래스를 상속받고, metaclass는 type으로 받는다. 그래서 여러 class에 type()
을 해보면 type이 결과로 나온다.
>>> type(list)
type
>>> type(int)
type
>>> type(A)
type
class는 callable이기 때문에 괄호를 붙일 수 있다. class에 괄호를 붙인다는 것은 인스턴스화를 의미한다. 인스턴스화는 클래스에 맞게 구체적인 값을 만든느 것이며 python에서는 인스턴스화 방법으로 데이터를 만든다. 그래서 데이터를 정의하는 방법은 리터럴 방식과 인스턴스화 방식이 있다.
a = [1, 2, 3] # 리터럴 방식
b = list([1, 2, 3) # 인스턴스화
a = 3 # 리터럴 방식
b = int(3) # 인스턴스화
a = A()
를 인스턴스화로 해석하면 다음과 같다. A라는 형태로 값을 만드는데, 이름을 a라고 해라!
class로 객체를 만들 때 괄호를 붙여 인스턴스화 방식으로 객체를 만든다고 했다. 그렇다면 class 내부적으로는 어떤 동작을 할까? class 객체가 생성될 때(= 인스턴스화) 내부적으로는 __new__와 __init__ method가 실행된다. 이 둘은 모두 object에서 상속받은 것들이다.
class BB:
def __new__(self):
print('new')
return super().__new__(self)
def __init__(self):
print('init')
class에 괄호가 붙는 순간 __new__가 실행된다. __new__의 역할은 미리 정의된 대로 값을 만드는 역할이다. 그리고 나서 __init__이 실행되어 사용자가 지정한 대로 값을 초기화 한다. 이 두 method가 동작한 후에야 객체가 만들어지고, 이 두 method를 합쳐 생성자라고 한다.
따라서 BB 객체를 만들면 아래와 같이 __new__와 __init__이 둘 다 실행되는 것을 볼 수 있다.
>>> bb = BB()
new
init
isntance method와 class method를 정의한 것을 보면 항상 self와 cls가 쓰인다. 이것은 무슨 의미일까?
class A:
def t(self):
return '오리지널 사용법'
instance method와 class method의 차이는 해당 method를 누가 사용하느냐에 달려있다. 객체가 사용하면 instance method, class가 사용하면 class method이다. 그런데 name space 관점에서 보면 조금 다르다.
aa = A()
A.t(aa) # 원래 사용법
name space 관점에서 보면 A.t()
안에는 객체가 들어와야 한다. 따라서 method도 aa.t(aa)
이렇게 쓰는게 맞다. 그런데 python에서는 중복을 싫어하므로 사용할 때에는 괄호 안에 객체를 생략할 수 있게 만들어놓았다. 하지만 method를 선언할 때에는 객체가 필요함을 명시해야하므로 self와 cls를 써주는 것이다.