접근 제어자
파이썬에서는 접근 제어자가 없다. 하지만 일종의 접근 제어자를 흉내낼 수 있는 몇 가지 관례가 있다. 예를 들어, 변수명 앞에 밑줄을 그어(leading underscore) private으로 취급하는 식이다. 관습적으로 이러한 변수는 단위 테스트 등이 아니라면 직접 수정하면 안 된다.
이 방법이 실제로 변수의 접근 제어자 역할을 하지 못한다는 사실에 유의하자.
Sample Code
class Connector:
def __init__(self, source):
self.source = source # public
self._timeout = 60 # private
conn = Connector("postgresql://localhost")
print(conn.__dict__)
Python
복사
Console
>> {'source': 'postgresql://localhost', '_timeout': 60}
Python
복사
Naming Mangling
파이썬은 클래스 내부에서 변수의 이름 앞에 두 개의 밑줄을 그으면 특이한 일이 생긴다. 이름 Naming Mangling이라고 하는데, 아래와 같이 변수의 이름이 <Class-Name>_<Variable-Name>처럼 변하게 된다.
class Connector:
def __init__(self, source):
self.source = source # public
self.__timeout = 60 # private
conn = Connector("postgresql://localhost")
print(vars(conn))
print(conn._Connector__timeout)
Python
복사
Console
>> {'source': 'postgresql://localhost', '_Connector__timeout': 60}
Python
복사
Naming Mangling은 여러 번 확장되는 클래스의 메서드를 충돌 없이 오버라이드 하기 위해 만들어진 개념이다. 변수를 private으로 이용하기 위한 개념이 아니므로 접근 제어의 목적으로 사용하면 안된다.
파이썬에서 밑줄이 같는 재미있는 특성들이 있는데, 이는 다른 포스트에서 정리하도록 하겠다.
(맛보기: _, _var, __init__, map_, _naming__mangling, …)
Property
Getter and Setter
파이썬에서는 객체의 캡슐화를 위해 setter와 getter를 설정할 수 있다. 이를 통해 변수의 유효성 검사 등을 진행하는 데 도움을 받을 수 있다.
자유도가 높은 파이썬답게, 필수는 아니다.
파이썬에 내장된 @property 데코레이터는 함수명을 변수명으로 하는 Read-Only Class Attribute를 만든다. 그리고 해당 속성이 호출되면, 함수에서 return 하는 값을 반환하므로 get 기능을 수행한다고 볼 수 있다. 또한, @<attribute-name>.setter 를 호출하면 같은 맥락에서 setter 기능을 수행할 수 있다.
•
통상적인 getter와 setter의 기능을 수행하기 때문에 해당 값이 valid 유무(이메일의 경우 형식에 맞는지) 등을 체크할 수 있다.
•
명령-쿼리 분리 원칙(command and query separation - CC08)을 따르기 좋다
•
멱등성 (idempotent - 연산을 반복해도 결과가 같은 성질)
•
유일한 예외는 lazy property (미리 계산된 값을 사용)
만약 Practical하게 사용하고자 한다면 아래 코드를 템플릿으로 사용할 수 있을 듯하다.
Code for Practical Use
class Coordinate:
def __init__(self, lat:float, long:float) -> None:
self._latitude = self._longitude = None
self.latitude = lat
self.longitude = long
@property
def latitude(self) -> float:
return self._latitude
@latitude.setter
def latitude(self, lat_value: float) -> None:
if -90 <= lat_value <= 90:
self._latitude = lat_value
else:
raise ValueError(f"유효하지 않는 위도 값: {lat_value}")
@property
def longitude(self) -> float:
return self._longitude
@longitude.setter
def longitude(self, long_value: float) -> None:
if -180 <= long_value <= 180:
self._longitude = long_value
else:
raise ValueError(f"유효하지 않은 경도 값: {long_value}")
coord = Coordinate(37.5024625930912, 127.02862987558395)
Python
복사
Playground Code
# test
class PropertyTest:
def __init__(self, val):
self.val = val
@property
def haha(self):
print('accessing haha value!')
return self.val
@haha.setter
def haha(self, new_val):
print(f'set new val with: {new_val}')
self.val = new_val
test = PropertyTest(10)
test.haha = 1
print(test.haha)
Python
복사
console
>> set new val with: 1
>> accessing haha value!
>> 1
Python
복사
dataclasses 모듈 (PEP-557)
파이썬에서 클래스를 작성하다보면 다음과 같이 불필요한 코드를 작성해야할 때가 많다.
class TestClass:
def __init__(self, x, y):
self.x = x
self.y = y
Python
복사
이런 코드는 보일러 플레이트 (boilerplate) 혹은, 보이러 플레이트 코드라고 한다. Python 3.7부터는 dataclasses 모듈의 dataclass 데코레이터, field를 이용하여 중복되는 코드를 제거하고 단순화할 수 있다.
주의! annotation이 없으면 클래스 변수로 처리한다.
Sample Code
구체적인 내용을 살펴보기 전에 dataclass와 field를 이용한 예시를 먼저 살펴보자.
from dataclasses import dataclass, field
@dataclass
class MyDataClass:
writer = "Learn Err Day" # 클래스 변수
# writer = field(init=False) # 이렇게 클래스 변수처럼 남길 수도 있다
# writer: str # 하지만 이러면 인스턴스 변수
title: str # 인스턴스 변수, 반드시 할당해줘야 함
# shared_list: list = [] # err (아래 log 확인)
unshared_list: list = field(default_factory=lambda: ["hello", "world"])
def __post_init__(self):
# validation check here!
print("all data set!")
Python
복사
Things to Know
dataclass를 사용하기 위해 알아야 할 점은 다음과 같다.
•
__init__ 매직 매서드를 자동으로 호출해준다.
•
annotation이 없으면 인스턴스가 해당 변수를 공유한다.
•
비슷한 맥락으로 리스트 등의 mutable한 값을 기본 값으로 할당할 수 없다.
"""
ValueError: mutable default <class 'list'> for field shared_list is not allowed: use default_factory
raise ValueError(f'mutable default {type(f.default)} for field ...
"""
Python
복사
•
mutable한 값을 할당하기 위해서는 field(default_factory)를 사용해야한다. 이를 통해 인스턴스를 생성할 때 [”hello”, “world”]를 생성하여 shared_list라는 값에 할당하게 된다.
id(instance1.unshared_list) != id(instance2.unshared_list)
Python
복사
•
자동으로 호출된 init 매직메서드 이후, __post_init__이 호출됨을 이용해서 validation 등의 작업을 처리할 수 있다.