끄적 코딩

[파이썬] 함수의 매개변수 선택 입력하려면 : using mutable objects as default parameter value, 인자의 기본 값, 선택적 인수 본문

문제해결

[파이썬] 함수의 매개변수 선택 입력하려면 : using mutable objects as default parameter value, 인자의 기본 값, 선택적 인수

몽.글렛 2022. 3. 30. 00:21

 

메이킹 프로젝트 <집근처 전시회>의 함수처리에 대한 문제 해결이다. 

 

같은 로직의 A,B
A의 경우 마지막 조건문에 따라 결과가 달라지는데, 이 조건에 사용할 인자(매개변수)를 하나 더 필요로 한다.
즉, A에는 func(a,b,c) B에는 func(a,b)를 사용해야될 경우이다.

 

이 경우, 함수를 선언할 때 인자 c의 기본값을 설정하면 해결된다. 

def func(a, b, c="내가 설정한 기본값"):

     함수 실행부 

기본값을 설정하면 a,b만 값을 받아서 사용하고, c는 초기 설정값으로 함수를 실행시킨다.

 

입력 순서 주의사항!

"Non-default argument follows default argument"
매개변수가 여러개일 때, 필수로 입력해야하는 매개변수를 기본값 변수보다 먼저 사용해야한다.

 

가변객체일 경우 주의사항!

list, dict, set 과 같은 변하는 객체의 경우 매개변수 기본값을 None으로 작성 후, 함수 내부에서 다시 정의한다.
def func(c = None):
    if c is None:
        c = []
    함수 실행부​

<참고> 발생하는 문제 자세히 보기 : Using mutable objects as default argument values in python

 

Programming FAQ — Python 3.10.4 documentation

Self is merely a conventional name for the first argument of a method. A method defined as meth(self, a, b, c) should be called as x.meth(a, b, c) for some instance x of the class in which the definition occurs; the called method will think it is called as

docs.python.org

 구글링 키워드 : '선택적 인수' , '인자의 기본값', 'default parameter value' 

 

  • 매개변수(Parameters) : 함수 선언시 () 속에 사용되는 변수
  • 인자(Arguments) : 함수 호출시 () 속에 사용되는 변수

def sum('Parameters') :

sum("Arguments")

 

  • 가변객체(mutable) tuple, int, float, bool, str 등
  • 불변객체(immutable) list, dict, set 등

 

사용전 / 사용후

파란색 부분이 같은 부분이다. 사용전(왼쪽)과 사용후(오른쪽)의 길이가 반이상 줄었다. 더군다나 보라색은 한 화면으로는 다 담기지 않을 정도의 엄청난 차이를 보여준다. 이전 코드와 비교하면 훨씬 깔끔해졌다.

 

 


 

 <case 1> 
비회원 : 일반 마커 생성
회원
: 북마크 데이터가 있는 경우 북마크용 마커 생성 + 그 외 일반 마커 생성

 <case 2> 
한장소에 1가지 전시
한장소에 n가지 전시

 

비회원인 경우는 조건 없이 <case 2>에 따라 다른 for문이 실행된다. 하지만, 회원일 경우는 비회원일 경우와 같은 for문이 실행되지만 북마크라는 조건(userbm_coordinate)에 따라 마커 모양을 다르게 생성 된다.

 

#회원은 북마크 마커와 일반 마커
if((get_markdata['lt'], get_markdata['lg']) in userbm_coordinate):
	folium.Marker(location=[get_markdata['lt'], get_markdata['lg']], popup=popup_html, tooltip=get_markdata['p'], icon=folium.Icon(color='darkblue', icon='bookmark')).add_to(map)
else:
    folium.Marker(location=[get_markdata['lt'], get_markdata['lg']], popup=popup_html, tooltip=get_markdata['p'], icon=folium.Icon(color='blue')).add_to(map)
    
#비회원은 일반 마커
folium.Marker(location=[overlap_markdata['lt'],overlap_markdata['lg']], popup=popup_html, tooltip=overlap_markdata['p'], icon=folium.Icon(color='blue')).add_to(map)

 

1차로 함수화할 때는 이 방법을 몰라서 위의 조건문 직전에 리턴값을 반환하는 함수를 사용했었다.

folium.Marker(리턴값 사용하는 부분).add_to(map) 

 

문제해결 전 코드 보기
#함수 : 전시 폴리움 마커 인자 생성(1종류 전시/ n종류 전시)
def make_args_onexhibit(one_data):
    get_markdata = pick_value(one_data)

    summary_info = folium.Html(f"""<div class="map_inner">
                    {p_tag(get_markdata['t'],get_markdata['pr'],get_markdata['lt'],get_markdata['lg'],get_markdata['p'])}
                </div>""", script=True)
    popup_html = folium.Popup(summary_info, max_width=500)

    marker_args= {
        "latitude" : get_markdata['lt'],
        "longitude" : get_markdata['lg'],
        "popup" : popup_html,
        "tooltip" : get_markdata['p']
    }
    return marker_args

def make_args_multiexhibit(overlap_datas):
    popup_msg = []
    for overlap_one in overlap_datas:
        overlap_markdata = pick_value(overlap_one)
        target_info = f"""{p_tag(overlap_markdata['t'],overlap_markdata['pr'],overlap_markdata['lt'],overlap_markdata['lg'],overlap_markdata['p'])}"""
        popup_msg.append(target_info)
    popup_msg = ''.join(popup_msg)
    full_text = f"""<div class="map_inner">{popup_msg}</div>"""

    summary_info = folium.Html(f"""{full_text}""", script=True)
    popup_html = folium.Popup(summary_info, max_width=500)

    marker_args= {
        "latitude" : overlap_markdata['lt'],
        "longitude" : overlap_markdata['lg'],
        "popup" : popup_html,
        "tooltip" : overlap_markdata['p']
    }
    return marker_args
# 지도 검색 API
@app.route('/setposition', methods=['POST'])
def set_position():

    #지역 좌표 추출
    address1_recieve = request.form['address1_give']  # "광주시"
    address2_recieve = request.form['address2_give']  # "북구"
    set_location = db.region_info.find_one({'address_class1': address1_recieve, 'address_class2': address2_recieve})
	
    #지도 생성
    map = make_map(set_location['latitude'], set_location['longitude'])
	
    #DB데이터 추출
    total_data = list(db.exhibition_info.find({}, {'_id': False}))
	
    #DB데이터 중복 좌표 모음
    overlap_coordSP = overlap_list(total_data)

    # 회원 마크 표시
    if(request.form['key_give'] != ''):
        key_receive = request.form['key_give']
        
        # 회원 북마크
        userbm_coordinate = make_bmcoordinate(key_receive)

        # 한 장소에 1종류 전시(회원 마크 표시)
        for data in total_data:
            if "latitude" in data:
                if((data['latitude'], data['longitude']) not in overlap_coordSP):
                    one_markdata_login = make_args_onexhibit(data)

                    if((one_markdata_login['latitude'], one_markdata_login['longitude']) not in userbm_coordinate):
                        folium.Marker(location=[one_markdata_login['latitude'], one_markdata_login['longitude']], popup=one_markdata_login['popup'], tooltip=one_markdata_login['tooltip'], icon=folium.Icon(color='blue')).add_to(map)
                    else:
                        folium.Marker(location=[one_markdata_login['latitude'], one_markdata_login['longitude']], popup=one_markdata_login['popup'], tooltip=one_markdata_login['tooltip'], icon=folium.Icon(color='darkblue', icon='bookmark')).add_to(map)

        # 한 장소에 n종류 전시(회원 마크 표시)
        for coordinate in overlap_coordSP:
            overlap_datas = list(db.exhibition_info.find(
                {'latitude': coordinate[0], 'longitude': coordinate[1]}, {'_id': False}))
            multi_markdata_login = make_args_multiexhibit(overlap_datas)
            
            if((multi_markdata_login['latitude'], multi_markdata_login['longitude']) not in userbm_coordinate):
                folium.Marker(location=[multi_markdata_login['latitude'], multi_markdata_login['longitude']], popup=multi_markdata_login['popup'], tooltip=multi_markdata_login['tooltip'], icon=folium.Icon(color='blue')).add_to(map)
            else:
                folium.Marker(location=[multi_markdata_login['latitude'], multi_markdata_login['longitude']], popup=multi_markdata_login['popup'], tooltip=multi_markdata_login['tooltip'], icon=folium.Icon(color='darkblue', icon='bookmark')).add_to(map)

    # 비회원 마크 표시
    else:
        # 한 장소에 1종류 전시(비회원 마크 표시)
        for data in total_data:
            if "latitude" in data:
                if((data['latitude'], data['longitude']) not in overlap_coordSP):
                    one_markdata = make_args_onexhibit(data)

                    folium.Marker(location=[one_markdata['latitude'], one_markdata['longitude']], popup=one_markdata['popup'], tooltip=one_markdata['tooltip'], icon=folium.Icon(color='blue')).add_to(map)

        # 한 장소에 n종류 전시(비회원 마크 표시)
        for coordinate in overlap_coordSP:
            overlap_datas = list(db.exhibition_info.find(
                {'latitude': coordinate[0], 'longitude': coordinate[1]}, {'_id': False}))
            multi_markdata = make_args_multiexhibit(overlap_datas)
            
            folium.Marker(location=[multi_markdata['latitude'], multi_markdata['longitude']], popup=multi_markdata['popup'], tooltip=multi_markdata['tooltip'], icon=folium.Icon(color='blue')).add_to(map)
         
    map.save(r'/home/ubuntu/MakingChallenge11/exhibi-dev/templates/position_map.html')
    return jsonify({'result': 'success'})

 

함수 작업을 했는데도 이렇게나 길고 깔끔하지 못했다. 억지고 리턴값을 만들어서 사용하다보니 folium.Marker(리턴값 사용하는 부분).add_to(map) 부분도 복잡해지고, 반복되는 코드는 여전히 남아있었다. 게다가 지도 관련 API가 3개나 반복되었다. 유지보수하려면 크나큰 손실이었다. 생각보다 많이 거슬렸나보다... 꿈에서 나올 정도였다. 

 

이 문제는 함수의 파라미터값 하나만 처리하면 되는 문제였다. 어디선가 함수 선언시, ( )안의 변수 = False 같은 형태를 본적이 있었는데, 내 코드의 경우에는 적용이 안되었다. def func(변수 = False 혹은 True):

여러가지 방법으로 구글링을 하다가 '선택적 인수'라는 키워드를 발견했다.

 

북마크 관련한 변수 userbm_coordinate는 리스트 형태의 값으로 표현되어야 함으로 빈 리스트로 초기 선언을 했었다. 하지만, 파이썬에서 매개변수를 None으로 선언 후 함수 내부에서 재정의하는 방식을 권고한다. 나의 경우 빈 리스트로 선언해도 오류가 발생하지 않았지만, 함수를 여러번 사용시 가변객체 부분이 의도하지 않은 값으로 나타날 수 있기 때문에 파이썬 공식 문서에서 권하는 방법으로 함수 내부에서 if userbm_coordinate is None: 을 사용하여 빈 리스트로 변경하였다.

 

함수 속에 if 조건문까지 포함하여 리턴값 없이 마커를 생성하는 함수로 표현했다. 리턴값을 사용하려고 변수를 만들 필요없이, 함수만으로 결과값을 생성할 수 있었다. 

 

#함수: 전시 폴리움 마커(1종류 전시)
def mark_onexhibit(map, total_data,overlap_coords, userbm_coordinate = None):

    #가변객체일때 매개변수 기본값 설정
    if userbm_coordinate is None:
        userbm_coordinate = []
        
    for data in total_data:
        if "latitude" in data:
            if((data['latitude'], data['longitude']) not in overlap_coords):
                get_markdata = pick_value(data)

                summary_info = folium.Html(f"""<div class="map_inner">
                                {p_tag(get_markdata['t'],get_markdata['pr'],get_markdata['lt'],get_markdata['lg'],get_markdata['p'])}
                            </div>""", script=True)
                popup_html = folium.Popup(summary_info, max_width=500)

                if((get_markdata['lt'], get_markdata['lg']) in userbm_coordinate):
                    folium.Marker(location=[get_markdata['lt'], get_markdata['lg']], popup=popup_html, tooltip=get_markdata['p'], icon=folium.Icon(color='darkblue', icon='bookmark')).add_to(map)
                else:
                    folium.Marker(location=[get_markdata['lt'], get_markdata['lg']], popup=popup_html, tooltip=get_markdata['p'], icon=folium.Icon(color='blue')).add_to(map)

#함수: 전시 폴리움 마커(n종류 전시)
def mark_multiexhibit(map, overlap_coords, userbm_coordinate = None):

    #가변객체일때 매개변수 기본값 설정
    if userbm_coordinate is None:
        userbm_coordinate = []
        
    for coordinate in overlap_coords:
        overlap_datas = list(db.exhibition_info.find(
                    {'latitude': coordinate[0], 'longitude': coordinate[1]}, {'_id': False}))
        popup_msg = []
        for overlap_one in overlap_datas:
            overlap_markdata = pick_value(overlap_one)
            target_info = f"""{p_tag(overlap_markdata['t'],overlap_markdata['pr'],overlap_markdata['lt'],overlap_markdata['lg'],overlap_markdata['p'])}"""
            popup_msg.append(target_info)
        popup_msg = ''.join(popup_msg)
        full_text = f"""<div class="map_inner">{popup_msg}</div>"""

        summary_info = folium.Html(f"""{full_text}""", script=True)
        popup_html = folium.Popup(summary_info, max_width=500)

        if((overlap_markdata['lt'],overlap_markdata['lg']) in userbm_coordinate):
            folium.Marker(location=[overlap_markdata['lt'],overlap_markdata['lg']], popup=popup_html, tooltip=overlap_markdata['p'], icon=folium.Icon(color='darkblue', icon='bookmark')).add_to(map)
        else:
            folium.Marker(location=[overlap_markdata['lt'],overlap_markdata['lg']], popup=popup_html, tooltip=overlap_markdata['p'], icon=folium.Icon(color='blue')).add_to(map)
# 지도 검색 API
@app.route('/setposition', methods=['POST'])
def set_position():

    #지역 좌표 추출
    address1_recieve = request.form['address1_give']  # "광주시"
    address2_recieve = request.form['address2_give']  # "북구"
    set_location = db.region_info.find_one({'address_class1': address1_recieve, 'address_class2': address2_recieve})
	
    #지도 생성
    map_sp = make_map(set_location['latitude'], set_location['longitude'])
	
    #DB 데이터 추출
    total_data = list(db.exhibition_info.find({}, {'_id': False}))

    #DB데이터 중복 좌표 모음
    overlap_coordSP = overlap_list(total_data)

    # 회원 마크 표시
    if(request.form['key_give'] != ''):
        key_receive = request.form['key_give']
        
        #회원 북마크
        userbm_coordinate = make_bmcoordinate(key_receive)

        mark_onexhibit(map_sp, total_data, overlap_coordSP, userbm_coordinate)
        mark_multiexhibit(map_sp, overlap_coordSP, userbm_coordinate)

    # 비회원 마크 표시
    else:
        mark_onexhibit(map_sp, total_data, overlap_coordSP)
        mark_multiexhibit(map_sp, overlap_coordSP)

    map_sp.save(r'/home/ubuntu/MakingChallenge11/exhibi-dev/templates/position_map.html')
    return jsonify({'result': 'success'})

 

Comments