[파이썬] 파이썬 클린코드 - 파이썬스러운 코드

Yohan_05·2023년 5월 1일
0

python

목록 보기
4/4

이 글은 위키독스의 파이썬 틀린코드를 보고 공부한 내용을 정리한 글입니다

파이썬스럽다는 코드는 뭘까?

파이썬 고유한 매커니즘을 따른 코드를 파이썬스러운 코드라고 말한다.

Pythonic한 코드를 작성해야하는 이유?

보통 관용적인 방식으로 코드를 작성했을 때 성능이 좋고 코드도 짧으며 이해가 쉽다. 또한 동일한 패턴과 구조에 익숙해지면 실수를 줄이고 본질에 집중할 수 있다.

인덱스와 슬라이스

파이썬 인덱싱

파이썬 일부 데이터나 구조에서 요소에 접근할 때 인덱스를 활용할 수 있다. 그리고 파이썬은 다른 언어와 달리 음수를 사용하여 배열의 끝에서부터 접근이 가능하다.

nums = (1, 1, 2, 3, 5, 8, 13, 21)

>>> nums[-1]
21
>>> nums[-5]
3

파이썬 슬라이싱

또한 파이썬은 slice를 사용하여 특정 구간의 요소를 구할 수 있다. 슬라이싱에는 시작 인덱스, 끝 인덱스, 간격을 설정해줄 수 있다.

nums = (1, 1, 2, 3, 5, 8, 13, 21)

>>> nums[2:4]
(2, 3)
>>> nums[:-3]
(1, 1, 2, 3, 5)
>>> nums[::2]
(1, 2, 5, 13)

자체 시퀀스 생성

이런 파이썬의 인덱싱과 슬라이싱은 __getitem__ 이라는 매직 메서드로 동작한다. 특히 __getitem____len__ 을 사용하여 시퀀스나 이터러블 객체를 만들지 않고 키를 통해 객체의 특정 요소를 가져올 수 있다.

시퀀스란? : 데이터를 순서대로 하나씩 나열하여 나타낸 데이터 구조 list,tuple 같은 구조를 말한다.

컨텍스트 관리자

컨텍스트 관리자는 크게 두 가지 경우에 유용한 파이썬의 기능임.

리소스 관리

일반적으로 파일이나 소켓 연결을 열었을때 할당된 리소스를 모두 해제해주어야하는데, 이때 생각못한 오류가 날 수도 있다. 이를 사전에 모두 처리하는것은 어렵지만 파이썬에서는 with문 을 사용해 파이썬스럽게 구현 가능하다.

with open(filename) as fd:
    process_file(fd)

블록의 마지막이 실행되고 나면 컨텍스트가 종료되며, 오류가 있더라도 종료되므로 안전하게 실행할 수 있다.

코드 분리

주요 동작의 전후에 작업을 실행하려고 할 때나 독립적으로 코드를 분리해야 할 때 사용할 수 있다.

컨텐스트 관리자 구현

컨텍스트 관리자는 일반적으로 __enter____exit__두 개의 메서드만 구현하면되지만, contextlib 모듈을 사용하여 더 쉽게 구현할 수 있음.

__enter__ 메서드가 호출되면 새로운 컨텍스트로 진입하게 되며 컨텍스트의 마지막 문장이 끝나면 컨텍스트가 종료되며 처음 호출한 컨텍스트 관리자 객체의 __exit__ 메서드를 호출한다.

class dbhandler_decorator(contextlib.ContextDecorator):
    def __enter__(self):
        stop_database()

    def __exit__(self, ext_type, ex_value, ex_traceback):
        start_database()

@dbhandler_decorator()
def offline_backup():
    run("pg_dump database")

컨텍스트란? : 어떤 Task를 수행할 때 수행상태 및 정보를 를 기억하기 위해서 Task마다 코드 실행과 관련된 정보들을 구성하고 있는 것을 Context라고 한다

프로퍼티, 속성과 객체 메서드의 다른 타입

파이썬에서의 밑줄

일반적으로 파이썬에서의 변수나 메소드 이름 앞의 밑줄은 private를 의미한다. (private를 의미만하지 private하게 만들지는 않는다)

밑줄 두개는 name mangling 이라는 것을 실행한다. 말 그대로 이름을 만드는 것인데, _<class-name>__<attribute-name> 형태의 이름을 만든다. 이런 이름을 만드는 이유는 클래스가 여러 번 확장되더라도 충돌 없이 오버라이드를 하기 위한 것이다.

프로퍼티

프로퍼티는 아래의 두 경우 처럼 객체의 어떤 속성에 대한 접근을 제어하기 위해 사용한다.

  • 객체에 값을 저장해야 하는 경우
  • 객체의 상태나 다른 속성의 값으로 어떤 계산을 하려고 하는 경우

@property 데코레이터는 일반적인 getter와 역할이 같으며

@<property>.setter데코레이터는 setter와 역할이 같다.

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

>>> user = User("clean_user")
>>> user.email = "clean_user"
유효한 이메일이 아니므로 사용할 수 없음.
>>> user.email = "clean_user@example.com"
>>> user.email
'clean_user@example.com'

프로퍼티의 장점
프로퍼티를 사용하면 명령-쿼리 분리 원칙(command and query separation)을 따르기도 용이하다.

@property 데코레이터는 응답하기 위한 query이며

@<property>.setter데코레이터는 무언가를 하기 위한 command이다.

파이썬에서의 프로퍼티
일반적인 프로그래밍 언어는 public, private, protected 세 가지 프로퍼티를 가지지만

파이썬은 모든 프로퍼티와 함수가 public하다. 따라서 호출자가 모든 객체의 속성을 호출할 수 있다.

밑줄(언더스코어)을 사용하여 다른 언어처럼 private를 의미할 수 있지만, 여전히 호출은 할 수 있다.

이터러블 객체

  • 이러러블 __iter__ 매직 메서드를 구현한 객체
  • 이터레이터 : __next__ 매직 매서드를 구현한 객체.

파이썬은 기본적으로 반복 가능한 리스트 튜플등이 있음.

for e in myobject: 형태로 객체를 반복할 수 있는지 확인하기 위해 객체가 __iter____next__ 중 하나를 포함하는지와 객체가 시퀀스이고 __len____getitem__ 모두 가졌는지를 검사한다.

컨테이너 객체

__container__() 메서드를 구현한 객체, 일반적으로 Boolean 값을 반환하도록 구현. 해당 키워드는 in 키워드가 발견될 때 호출됨.

사용 예

x,y 를 멤버변수로 갖고 있는 coord 가 그리드의 영역에 있는지 검사하고 표시하고 싶을 때, 일반적인 구현은 아래와 같다.

# less Pythonic
def mark_coordinate(grid, coord):
	if 0 <= coord.x < grid.width and 0 <= coord.y < grid.height:
    	grid[coord] = MARKED
  • 이렇게 짜면 직관적으로 x,y 가 무엇을 하는지 알아보기 힘듦.
### more Pythonic
class Boundaries:
	def __init__(self, width, height):
    	self.width = width
        self.height = height
        
    def __contains__(self, coord):
    	x, y = coord
        return 0 <= x < self.width and 0 <= y < self.height
        
class Grid:
	def __init__(self,width, height):
    	self.width = width
        self.height = height
        self.limit = Boundaries(width, height)
    
    def __containes__():
    	return coord in self.limits
# Usage
def mark_coordinate(grid, coord0:
	if coord in grid:
    	grid[coord] = MARKED
  • in 절을 통해 직관적으로 Grid 안에 있는지 체크하는 듯한 느낌을 받을 수 있음.

장점?
1. 외부에서 사용할 때 해당 코드들은 마치 파이썬이 문제를 해결한 것처럼 보임.
2. 구성이 간단하고 위음을 통해 문제를 해결함. (객체들이 최소한의 논리를 사용)

객체의 동적 속성

동적으로 어떤 값을 자료 구조에 담고 싶다면 Dictionary 를 이용하자. 딕셔너리는 불변의 값, 즉 수정이 불가능한 자료를 키로 사용하여 자료를 저장하는 해쉬맵 자료구조이다.

클래스로 예시를 들어보자.

class Car:
	def __init__(self, name, color):
    	self.name = name
        self.color = color
        
my_car = Car("Ferarri", "red")
print(f"name:{my_car.name} color:{my_car.color}")

getattr

my_car.name 을 호출하면 파이썬은 객체의 사전에서 name을 찾아서 __getattribute__ 를 호출한다.

class DynamicAttributes:
	def __init__(self, attribute):
    	self.attribute = attribute
    
    def __getattr__(self, attr):
    	if attr.startswith("fallback "):
        	name = attr.replace("fallback_", "")
            return f"[fallback resolbed] {name}"
        raise AttributeError(f"{self.__class__.__name__}에는 {attr} 속성이 없음.")

호출

dyn = DynamicAttributes("value")
>>> print(dyn.attribute)
'value'

🔼 attribute 호출

>>> dyn.fallback_test
'[fallback resolved] test'

🔼 멤버변수에 없는 값을 호출

>>> dyn.__dict__["fallback_new"] = "new value"
>>> dyn.fallback_new
'new value'

🔼 새로운 멤버변수를 동적으로 생성하기.

>>> getattr(dyn, "something", "apple")
'apple'

__getattr__은 파이썬 내장 함수 getattr()에도 영향을 미친다.

호출혁 (callbale) 객체

클래스 명 뒤에 괄호를 붙이면 생성자 메서드 __init__ 이 실형되며, 결과물인 인스턴스를 반환한다. __call__ 함수를 구현하면 instance 변수 뒤에 괄호가 올 경우 동작을 정의할 수 있다.

from collections import defaultdict

class CallCount:
	def __init__(self):
    	self._counts= defaultdict(int)
    def __call__(self, argument):
    	self._counts[argument] += 1
        return self._counts[argument]

호출

>>> cc = CallCount()
>>> cc(1)
11
>>> cc(2)
1
>>> cc(1)
2
>>> cc(1)
3
>>> cc('something')
1

파이썬에서 피해야할 점

변경 가능한 파라미터 기본값

파라티터에서 디폴트값을 설장하는 것은 매번 파라미터를 설정해야하는 개발자의 피로를 줄여줌. 다만 수정이 가능한 변수로 설정하면 문제가 될 수 있음.

def say_name(name = "Giraffe"):
	print(name)

🔼 다른 언어를 하다 온 사람이면 문제가 있는 함수라고 볼 수 있지만 문제될건 없다.

왜?

  • 왜냐면 name[0] = "R" 과 같은 조작이 파이썬 문자열에서 불가능하기 때문.
  • 문자열은 주소가 없는 자료형(말 그대로 값)을 의미하는 원시 자료형처럼 취급된다.

개발자는 항상 매개변수의 default 에는 변경 불가능한 값을 선언하는 것이 좋다. (None 등으로 default 를 설정한다던가 말이다)

내장 (built-in) 타입 확장

파이썬 내장 빌트인 타입은 클래스의 매서드를 서로 호출하지 않기 때문에 하나를 오버라이드하면 나머지에는 반영되지 않아서 예기치 못한 결과가 발생함.

따라서 dict,list, set과 같은 파이썬 내장 빌트인 클래스를 직접 확장하는것은 피하도록 하자.

profile
안녕하세요 DevOps 엔지니어로 현업에서 활동중인 요한이라고 합니다.

0개의 댓글