my_list = [i for i in range(10)]
print(my_list[1:9:2])
interval = slice(1,9,2)
print(my_list[interval])
[1, 3, 5, 7]
[1, 3, 5, 7]
__getitem__
, __len__
)을 구현했기 때문이다.__getitem__
을 구현해야 하는데, 이때 행위는 직접 하는 것 보단 리스트라면 리스트에 위임하는 게 좋다.__getitem__
예시에서 주석문처럼 하지 말라는 뜻인듯.class Items:
def __init__(self, *values):
self._values = list(values)
def __len__(self):
return len(self._values)
def __getitem__(self, item):
return self._values.__getitem__(item)
# return self._values[item]
items = Items(1,2,3,4,5)
print(items[0])
1
# 1번 예시
filename = "some_file"
fd = open(filename)
try:
do_something
finally:
fd.close()
# 2번 예시
with open(filename) as fd:
do_something
__enter__
메서드와 __exit__
메서드로 구성된다.__enter__
문이 뭔가를 반환하지 않아도 되긴 하다. 반환해도 as 뒤 변수에 할당하지 않아도 된다.__exit__
이 호출된다.def stop_database():
run("systemctl stop postgresql.service")
def start_databse():
run("systemctl start postgresql.service")
class DBHandler:
def __enter__(self):
stop_database()
return self
def __exit__(self, exc_type, ex_value, ex_traceback):
start_databse()
def db_backup():
run("pg_dump database")
def main():
with DBHandler():
db_backup()
__enter__
에서 무언가를 반환하는 것이 좋다.(좋은 습관)__exit__
의 param을 주목하자. 예외가 발생하지 않으면 모든 param은 None값이다.__exit__
리턴값을 잘 생각해야 한다. 실수로 __exit__
에서 True를 반환하지 않도록 주의해야 한다.__exit__
, __enter__
프로토콜을 구현해서 context manager를 만들 수도 있고,__enter__
가 되고, 뒤는 __exit__
이 된다.import contextlib
@contextlib.contextmanager
def db_handler():
stop_database()
yield
start_databse()
with db_handler():
db_backup()
__enter__
의 리턴값을 변수에 적용할 수 없다.import contextlib
class dbhandler_decorator(contextlib.ContextDecorator):
def __enter__(self):
stop_database()
def __exit__(self, exc_type, exc_val, exc_tb):
start_databse()
@dbhandler_decorator()
def offline_backup():
run("pg_dump database")
import contextlib
with contextlib.suppress(DataConversionExceiption):
parse_data(input_json_or_dict)
💡 객체는 외부 호출 객체와 관련된 속성과 메서드만을 노출해야 한다.
즉 객체의 인터페이스로 공개하는 용도가 아니라면 모든 멤버에는 접두사로 하나의 밑줄을 사용하는 것이 좋다.
- 이유: 바깥에서 호출하지 않기에 안전하게 리팩토링 가능.
프로퍼티
- 객체관련 데이터는 일반적인 attribute쓰면 된다. (클래스 변수, 인스턴스 변수)
- 만약 객체 상태나 다른 속성의 값을 기반으로 어떤 계산된 값이 필요할 때가 있다.
- 이때는 property쓰면 된다!
- property는 객체의 어떤 속성에 대한 접근을 제어하려는 경우 사용한다.
- java에 getter, setter가 있다면 파이썬에서는 property
- propery는 cqrs법칙을 따르기도 좋은 법칙이다.
- @propery: 무언가에 답하기 위한 쿼리
- @(property_name).setter: 무언가를 하기 위한 쿼리
import re
EMAIL_FORMAT = re.compile(r"[^@]+@[^@]+[^@]+")
def is_valid_email(potentially_valid_email: str):
return re.match(EMAIL_FORMAT, potentially_valid_email) is not None
class User:
def __init__(self, username):
self.username = username
self._email = None
@property
def email(self):
return self._email
@email.setter
def email(self, new_email):
if not is_valid_email(new_email):
raise ValueError("값 잘못됨~")
self._email = new_email
💡 객체 모든 속성에 propery 적용할 필요는 없다. 대부분 인스턴스 변수, 클래스 변수로 충분하다.
속성 값을 가져오거나 수정할 때 특별한 로직이 필요한 경우에만 프로퍼티를 사용하자.
__iter__
구현한 객체__next__
구현한 객체__next__
나 __iter__
중 하나를 가졌는지 확인__len__
or __getitem__
를 모두 가졌는지 확인__iter__()
함수 호출__next__()
호출__iter__()
를 먼저 찾고 없으면 __getitem__
을 찾는다.__len__
과 __getitem__
을 구현하고, 인덱스 0부터 한 번에 하나씩 가져올 수 있어야 한다._contains__
메서드 구현한 객체. 일반적으로 Boolean값을 반환함 (in 연산)<myobject>.<myattribute>
을 실행하면 __getattribute__
가 실행된다.<myattribute>
이름을 파라미터로 전달하여 __getattr__
라는 추가 메서드가 호출된다.class DynamicAttributes:
def __init__(self, attribute):
self.attribute = attribute
def __getattr__(self, item):
if item.startswith("fallback_"):
name = item.replace("fallback_", "banana")
return f"[fallback resolved], {name}"
raise AttributeError(f"{self.__class__.__name__}에는 {item}속성이 없음")
dyn = DynamicAttributes("value")
print("dyn:", dyn.attribute)
print("dyn.fallback_test", dyn.fallback_test)
dyn.__dict__["fallback_new"] = "new value"
print(dyn.fallback_new)
print(getattr(dyn, "asdfasdf", "defalut"))
dyn: value
dyn.fallback_test [fallback resolved], bananatest
new value
defalut
💡
_getattr__
같은 동적 메서드를 구현할 땐 AttributeError를 발생시켜야 함을 주의하자.
__call__
구현.from collections import defaultdict
class CallCount:
def __init__(self):
self._counts = defaultdict(int)
def __call__(self, item, *args, **kwargs):
self._counts[item] += 1
return self._counts[item]
cc = CallCount()
print(cc(1))
print(cc(2))
print(cc(5))
print(cc(5))
print(cc(5))
1
1
1
2
3