흔한말 (collection)/Python

함수의 탄생: 문맥(Context)에서 기능(function)으로

hnanmal 2022. 3. 29. 00:49
300x250

프로그래밍은 보통 리스트(로 대변되는 반복 가능한 타입)를 받아서 그중 일부를 추려내고,
모든 원소에 동일한 변환작업을 한 다음 그 변환된 결과에서 어떤 의미를 추출해 내는 작업의 연속인 경우가 많습니다.

 

사례와 같이 봐야 더 정확한 설명이 되겠지만, 어떤 "의도"를 가지고 구현해 내는 코드는
상황에 따라서 결과값은 달라지지만, 그 본질적인 논리의 흐름은 변하지 않는 경우가 종종 있습니다.

 

코딩으로 문제를 해결하는 보통의 상황

무슨 말인지 좀 구체적으로 예를 들어볼까요?
어떤 공부 관련 커뮤니티에서 모의고사를 시행했는데, 그중 90점 이상의 성적을 낸 사람들에게 상품을 주려고 한다고 해보겠습니다.


상품을 주기 전에는 당연히 공지사항으로 어떤 사람이 당첨되었는지를 밝혀야 하는데,
아이디를 그대로 밝히면 개인정보 문제가 발생할 수 있으니, 아이디 중 일부는 별 표시로 가리고 줘야 한다고 해봅시다.

 

회원목록 = [
    {
    "이름": "김철수",
    "아이디": "chul_ss77",
    "나이": 26,
    "점수": 89,
    "가입일": 2022,
    },
    {
    "이름": "박지혜",
    "아이디": "jhpark5454",
    "나이": 23,
    "점수": 73,
    "가입일": 2015,
    },
    {
    "이름": "정지희",
    "아이디": "jihee7788",
    "나이": 29,
    "점수": 91,
    "가입일": 2017,
    },
    {
    "이름": "한규만",
    "아이디": "kyu310000",
    "나이": 28,
    "점수": 99,
    "가입일": 2018,
    },
    {
    "이름": "이종찬",
    "아이디": "bellfull_112",
    "나이": 33,
    "점수": 77,
    "가입일": 2018,
    },
    {
    "이름": "배지훈",
    "아이디": "know94power",
    "나이": 32,
    "점수": 94,
    "가입일": 2011,
    },
    {
    "이름": "지학고",
    "아이디": "83hakhak83",
    "나이": 41,
    "점수": 96,
    "가입일": 2012,
    },
    {
    "이름": "나백천",
    "아이디": "iam1001000",
    "나이": 45,
    "점수": 78,
    "가입일": 2019-11-25,
    },
    {
    "이름": "한설희",
    "아이디": "snowsmiley",
    "나이": 23,
    "점수": 89,
    "가입일": 2020,
    },
    {
    "이름": "백주선",
    "아이디": "drink456god",
    "나이": 30,
    "점수": 49,
    "가입일": 2017,
    },
]

커뮤니티의 모든 회원의 정보를 다 준비하기는 좀 어려우니 예시로 10명의 정보를 준비했습니다.

이제 간단한 for 문으로 회원들 중 90점 이상인 사람들을 대상으로 ID를 끝에 별표를 해서 반환하도록 해볼까요?

 

result = []

for a in 회원목록:
    if a["점수"] >= 90:
        수정아이디 = a["아이디"][:-3] + "***"
        result.append(수정아이디)

result
['jihee7***', 'kyu310***', 'know94po***', '83hakha***']

총 4명이 90점을 넘어서 이벤트 당첨 대상이 되었군요.
각 각의 아이디는 마지막 3자리를 별표 처리해서 공지문에 올릴 수 있는 상태로 데이터가 정리되었습니다.

 

지금까지의 단계를 좀 정리해볼까요?

 

  1. 대상 선별 (90점 이상만 필터링)
  2. 아이디 수정 (아이디 끝 3자리 마스킹)

 

이렇게 두 단계의 구성으로 이루어진 코드라는 것을 알 수 있죠?

 

조건이 달라져도 훌륭한 프로그래머에게는 아무런 문제가 없죠!

그런데 이렇게 이벤트를 성공적으로 잘 끝내고 나니, 또 다른 상황이 발생하게 됩니다.

사람들의 반응이 좋았던 나머지, 이번에는 점수의 커트라인을 조금 낮춰서 대상자를 더 포함하게 하고, 멤버십 혜택을 주도록 하자는 의견이 나온 것이죠.

 

다만 저번에 아이디를 공지하던 방식에서 아이디가 지나치게 많이 노출되었으니, 이번에는 끝에서 5자리를 마스킹 하자는 별도 주문까지 나왔습니다.

상황이 조금 달라졌지만 별거 없죠? 아까 작성한 코드를 복사해 와서, 필요한 부분만 수정해서 고쳐봅시다.

 

result = []

for a in 회원목록:
    if a["점수"] >= 80:
        수정아이디 = a["아이디"][:-5] + "*****"
        result.append(수정아이디)

result
['chul*****',
 'jihe*****',
 'kyu3*****',
 'know94*****',
 '83hak*****',
 'snows*****']

이번에도 멋지게 일을 끝냈군요. 그런데 일이 점점 복잡해지려나 봅니다.
앞으로는 이런 식으로 게릴라 이벤트가 한 달에 최대 5번까지도 발생할 수 있다고 위에서 언질이 내려왔거든요.
그리고 이제부터는 매번 아이디 마스킹하는 별표의 개수를 다르게 해야 할 것 같다는 주문도 내려왔습니다.

 

이제 슬슬 머릿속에서 이런 생각이 드실 겁니다.

 

"일 터질 때마다 저 코드를 수정하는 게 은근히 짜증 나는 데?

변하는 상황에 맞춰서 최소한의 정보만 수정하고, 기능 자체는 동일하게 유지할 수 있는 방법이 없을까?"

 

 

 

 

함수의 탄생: 문맥에서 기능으로

함수는 이런 생각이 들 때 비로소 도입하게 됩니다. 처음에는 어떤 상황을 해결하기 위해 하나의 문맥으로 코드를 작성했지만,
시간이 지남에 따라, 부분적으로 인풋을 변동시켜야 하고, 자주 반복해서 코드를 써야 하는 상황이 오게 되거든요.

 

이제 그동안 줄 글로 작성해오던 코드를 기능 단위로 함수로 바꿔봅시다.

그러기 위해서 아까 작성한 코드의 단계 구성을 다시 한번 살펴볼까요?

 

  1. 대상 선별 (x점 이상만 필터링)
  2. 아이디 수정 (아이디 끝 n자리 마스킹)

 

첫 번째 단계부터 함수로 한번 바꿔보겠습니다. 함수의 이름으로 단계 이름인 "대상 선별"을 그대로 사용할게요.
이 함수는 전체 인원 목록과 기준점수, 이 2가지의 인자를 받아들여 동작하는 함수로 설계했습니다.

 

def 대상선별(목록, 기준점수):
    result = filter(lambda a: a["점수"] >= 기준점수 ,목록)
    return list(result)

함수가 완성되었습니다. for 문을 그대로 활용해도 되지만, 예전 포스팅에서 다루었던 filter 함수를 사용해서 코드를 간단하게 구성했습니다.
(filter에 들어가는 조건으로 lambda 함수를 활용했는데, 아직 모르시는 분들은 그냥 넘어가셔도 됩니다. 람다 함수만 독립 주제로 포스팅할게요.)

 

for 문으로 함수를 구성하려면 아래와 같이 하면 됩니다.

def 대상선별(목록, 기준점수):
    result = []
    for a in 목록:
        if a["점수"] >= 기준점수:
            result.append(a)
    return result

동일한 역할을 하는 함수를 다른 방식으로 각각 구현해 보았죠? 잘 동작하는지는 실제로 회원 목록과 기준점수를 부여해서 살펴봅시다.

선별목록 = 대상선별(회원목록, 80)

선별목록
[{'이름': '김철수', '아이디': 'chul_ss77', '나이': 26, '점수': 89, '가입일': 2022},
 {'이름': '정지희', '아이디': 'jihee7788', '나이': 29, '점수': 91, '가입일': 2017},
 {'이름': '한규만', '아이디': 'kyu310000', '나이': 28, '점수': 99, '가입일': 2018},
 {'이름': '배지훈', '아이디': 'know94power', '나이': 32, '점수': 94, '가입일': 2011},
 {'이름': '지학고', '아이디': '83hakhak83', '나이': 41, '점수': 96, '가입일': 2012},
 {'이름': '한설희', '아이디': 'snowsmiley', '나이': 23, '점수': 89, '가입일': 2020}]

문제없이 동작하는 것을 확인했습니다.


이제 선별된 인원들의 아이디를 마스킹해서 공지하는 일만 남았죠?
2번째 단계인 "아이디 수정"도 함수화 해보겠습니다.

def 아이디수정(목록, 마스킹갯수):
    result = map(lambda a: a["아이디"][:-마스킹갯수] + "*" * 마스킹갯수, 목록)
    return list(result)

이번에도 마찬가지로 과거 포스팅했던 map 함수를 활용해서 만들었는데, for 문 기반으로 작성한 버전은 아래와 같습니다.

def 아이디수정(목록, 마스킹갯수):
    result = []
    for a in 목록:
        수정아이디 = a["아이디"][:-마스킹갯수] + "*" * 마스킹갯수
        result.append(수정아이디)
    return result
아이디수정(선별목록, 6)
['chu******',
 'jih******',
 'kyu******',
 'know9******',
 '83ha******',
 'snow******']

자, 이제 선별기준과 마스킹 기준이 계속 달라져도, 일일이 코드를 수정할 필요 없이 함수의 인풋 값만 조절하여 쉽게 일을 처리할 수 있게 되었습니다.

 

코드의 재사용성이 한층 더 높아지게 된 것이죠!!

 

이것이 가능했던 이유는, 대상 선별()이라는 함수와 아이디 수정()이라는 함수가 만들어질 때, 변하지 않는 논리의 흐름만 추상화시켜 함수 내부에 고정시키고,
나머지 변인들을 함수의 인자로 받아들이도록 설계되었기 때문입니다.

반응형