<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hwstar-InYourArea.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 08 Sep 2025 12:42:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hwstar-InYourArea.log</title>
            <url>https://velog.velcdn.com/images/hwstar-1204/profile/7cb8f12a-ede4-4e68-a052-c8b4b37e0a21/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hwstar-InYourArea.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hwstar-1204" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[508_AI_포텐데이] 참여후기 ]]></title>
            <link>https://velog.io/@hwstar-1204/508AI%ED%8F%AC%ED%85%90%EB%8D%B0%EC%9D%B4-%EC%B0%B8%EC%97%AC%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@hwstar-1204/508AI%ED%8F%AC%ED%85%90%EB%8D%B0%EC%9D%B4-%EC%B0%B8%EC%97%AC%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 08 Sep 2025 12:42:07 GMT</pubDate>
            <description><![CDATA[<h1 id="딱-10분만-프로젝트를-소개">&quot;딱 10분만&quot; 프로젝트를 소개</h1>
<p>지각에서 구원해 주는 &#39;나만의 AI 페이스메이커&#39;🏃</p>
<p>오늘도 ‘10분만 일찍 나올 걸…’이라고 생각하셨다면 당신은 프로 지각러입니다.</p>
<p>[딱 10분만]은 실행할 수 있는 계획 수립을 도와, 시간 인지 결핍으로 외출 준비 시간 관리에 어려움을 겪는 사람들의 지각을 방지하는 AI 페이스메이킹 서비스입니다.]</p>
<h4 id="핵심-기능">[핵심 기능]</h4>
<p>💡 내 루틴을 고려한 최적의 외출 준비 계획을 짠!
약속 시간, 약속 장소, 이동 수단 내 외출 루틴까지 모두 한번에 고려해요!
📣 너를 구원하노라…맞춤형 피드백(잔..잔소리)으로 실행을 도와요.
사용자의 맥락을 고려한 시각적, 청각적 피드백을 제공해요.</p>
<h4 id="고도화">[고도화]</h4>
<p>✍️ <strong>[기능 개선 1] 루틴 입력 UX 개선</strong>
불편했던 외출 루틴 입력 방법을 바꿨어요! 이제 직관적으로 내 루틴을 입력해 보세요!
🤖 <strong>[기능 개선 2] AI 기술 고도화</strong>
스케줄링 AI에 [Structured Ouput] 기술을 적용해서 성능을 개선했어요.
🗣️ <strong>[기능 추가 1] 음성형 피드백</strong>
[CLOVA Voice]로 잔소리 기능을 더 강화했어요. 이제는 구원신이 말로 실행을 도와요!
🎯 <strong>[기능 추가 2] 개인화 경험 제공</strong>
개인화된 경험을 제공하기 위해 회원 식별을 위한 로그인/회원가입 화면을 추가했어요.</p>
<h1 id="ncloud에서-어떤-서비스를-활용하셨나요">Ncloud에서 어떤 서비스를 활용하셨나요?</h1>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/d930b66c-e85d-4af9-95ec-f7cbea88a7fd/image.png" alt=""></p>
<p>그린 디벨로퍼 지원을 받아 저희 AI 서비스 운영에 필요한 서버를 Naver Cloud Platform의 여러 상품들을 사용하여 손쉽게 구성할 수 있었습니다.</p>
<ul>
<li>Server (VPC) : 서비스 운영 메인 서버</li>
<li>Cloud DB for PostgreSQL (VPC) : 사용자 데이터 및 루틴 정보 관리</li>
<li>CLOVA Studio (HCX-007 모델) : AI 스케줄링 모델 학습 및 적용</li>
<li>AI·NAVER API (CLOVA Voice) : 음성 피드백(페르소나 적용 TTS)</li>
<li>Application Services (GeoLocation) : 사용자 IP 기반 위치 정보 제공</li>
</ul>
<h1 id="q-ncloud-서비스를-어떻게-적용하였나요">Q. Ncloud 서비스를 어떻게 적용하였나요?</h1>
<p>서비스 인프라는 VPC 기반으로 구성했으며, 메인 서버와 Cloud DB for PostgreSQL을 연결하여 안정적인 서비스 운영 환경을 구축했습니다.
AI 모델은 CLOVA Studio의 HCX-007을 활용해 스케줄링 로직을 고도화했고, CLOVA Voice API를 통해 음성 피드백을 제공했습니다.
또한 GeoLocation API를 적용하여 사용자의 위치 정보를 기반으로 이동 시간을 보다 정확히 계산할 수 있도록 했습니다.</p>
<h1 id="q-ncloud-사용-중-특히-만족했던-점과-아쉬웠던-점은-무엇인가요">Q. Ncloud 사용 중 특히 만족했던 점과, 아쉬웠던 점은 무엇인가요?</h1>
<p>만족했던 점은 직관적인 VPC 네트워크 구성(UI 기반) 덕분에 Subnet, ACG 등을 빠르게 설정할 수 있었고, AI·API 서비스들을 연동하기 용이했다는 점입니다.
아쉬운 점은 일부 고급 기능에 대한 문서화 부족과, 사용 초기 진입장벽이 다소 높아 세부 설정을 이해하는 데 시간이 필요했다는 점입니다.</p>
<h1 id="q-green-developers-프로그램-참여-소감-말씀-부탁-드립니다">Q. Green Developers 프로그램 참여 소감 말씀 부탁 드립니다.</h1>
<p>AI와 클라우드 인프라를 결합해 실질적인 서비스를 운영해볼 수 있었던 소중한 경험이었습니다. 또한 지원 덕분에 안정적인 환경에서 개발을 진행할 수 있어 큰 도움이 되었습니다.</p>
<h1 id="q-마지막-한-말씀-부탁-드립니다">Q. 마지막 한 말씀 부탁 드립니다.</h1>
<p>이번 프로젝트를 통해 사용자의 실생활 문제인 지각 방지를 해결하면서, 기술이 삶을 직접적으로 개선할 수 있다는 점을 체감했습니다.
향후에는 Ncloud의 추가 AI API와 확장 가능한 인프라 기능을 더 적극 활용해 서비스를 고도화할 계획입니다. Green Developers 프로그램은 개발자에게 큰 도약의 기회를 주는 뜻깊은 경험이었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Brewbuds] DRF Permission 처리 정복 및 적용기]]></title>
            <link>https://velog.io/@hwstar-1204/Brewbuds-DRF-Permission-%EC%B2%98%EB%A6%AC-%EC%A0%95%EB%B3%B5-%EB%B0%8F-%EC%A0%81%EC%9A%A9%EA%B8%B0</link>
            <guid>https://velog.io/@hwstar-1204/Brewbuds-DRF-Permission-%EC%B2%98%EB%A6%AC-%EC%A0%95%EB%B3%B5-%EB%B0%8F-%EC%A0%81%EC%9A%A9%EA%B8%B0</guid>
            <pubDate>Tue, 13 May 2025 05:12:24 GMT</pubDate>
            <description><![CDATA[<p>초기에는 댓글을 삭제할 수 있는 권한이 댓글 작성자 본인에게만 있었습니다.
하지만 새로운 요구사항으로 인해, 댓글이 달린 게시글의 작성자도 해당 댓글을 삭제할 수 있어야 했습니다.</p>
<blockquote>
<p>✅ 최종 권한 조건
댓글 삭제 권한 = 댓글 작성자 + <strong>댓글이 달린 게시글의 작성자</strong></p>
</blockquote>
<h1 id="기존-권한-클래스">기존 권한 클래스</h1>
<pre><code class="language-python">class IsOwnerOrReadOnly(permissions.BasePermission):
    &quot;&quot;&quot;
    객체에 대한 읽기 권한은 모든 사용자에게 허용하고,
    수정/삭제 권한은 작성자에게만 허용하는 권한 클래스
    &quot;&quot;&quot;

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True

        return obj.author == request.user
</code></pre>
<p>이 권한 클래스는 댓글 작성자만 삭제할 수 있도록 제한하고 있기 때문에, 게시글 작성자가 댓글을 삭제하려는 새로운 요구사항을 만족하지 못했다.</p>
<p>이번 기회에 BasePermission이 어떤식으로 동작하는지 알아보고자 했다. 
먼저 이 base 권한 클래스를 살펴보자 </p>
<h1 id="basepermission-구조-살펴보기">BasePermission 구조 살펴보기</h1>
<pre><code class="language-python">class BasePermission(metaclass=BasePermissionMetaclass):
    &quot;&quot;&quot;
    A base class from which all permission classes should inherit.
    &quot;&quot;&quot;

    def has_permission(self, request, view):
        &quot;&quot;&quot;
        Return `True` if permission is granted, `False` otherwise.
        &quot;&quot;&quot;
        return True

    def has_object_permission(self, request, view, obj):
        &quot;&quot;&quot;
        Return `True` if permission is granted, `False` otherwise.
        &quot;&quot;&quot;
        return True</code></pre>
<p>이 클래스는 rest_framework/permissions.py 경로에 존재하며
drf에서 기본적으로 제공하는 여러 권한들이 이 클래스를 상속받아서 오버라이딩하고 있는것을 볼 수 있다. 
(DocString에서도 친절하게 설명해주고있다!)</p>
<p>이와 같이 나도 이 권한 클래스를 상속받아서 사용하면 될거 같다.
대충 감은 잡았으니 두 매서드가 무엇이고 어떻게 동작하는지 살펴보자 </p>
<p>그전에 APIView에서 어떤식으로 권한을 설정하는지 알아볼 필요가 있다. </p>
<h1 id="apiview에서-권한-설정-흐름">APIView에서 권한 설정 흐름</h1>
<pre><code class="language-python">class APIView(View):
    ...
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES

# api_settings의 DEFAULTS
DEFAULTS = {
    ...
    &#39;DEFAULT_PERMISSION_CLASSES&#39;: [
        &#39;rest_framework.permissions.AllowAny&#39;,
    ],
    ...</code></pre>
<p>APIView를 보면 기본적으로 rest_framework/settings.py에 DEFAULTS로 AllowAny 권한을 사용한다. </p>
<pre><code class="language-python"># project settings
REST_FRAMEWORK = {
    &#39;DEFAULT_PERMISSION_CLASSES&#39;: [
        &#39;rest_framework.permissions.IsAuthenticated&#39;,
    ]
}</code></pre>
<p>만약 사용자가 기본 권한 정책을 위와 같이 프로젝트의 settings에서 DEFAULT_PERMISSION_CLASSES를 재설정하면 그것을 사용하게된다.</p>
<h2 id="권한-체크-시점-check_permissions-vs-check_object_permissions">권한 체크 시점: check_permissions vs check_object_permissions</h2>
<pre><code class="language-python">#APIView
    def check_permissions(self, request):
        &quot;&quot;&quot;
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        &quot;&quot;&quot;
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request,
                    message=getattr(permission, &#39;message&#39;, None),
                    code=getattr(permission, &#39;code&#39;, None)
                )
...

    def get_permissions(self):
        &quot;&quot;&quot;
        Instantiates and returns the list of permissions that this view requires.
        &quot;&quot;&quot;
        return [permission() for permission in self.permission_classes]</code></pre>
<p>이후에 APIView가 <strong>Dispatch</strong>될때 initial 과정 중 self.check_permissions(request)를 실행하게된다.</p>
<p><code>self.permission_classes = [IsAuthenticated, IsAdminUser , etc...]</code></p>
<p>APIView에서 권한을 설정하려면 <em>self.permission_classes</em> 에 이런식으로 설정하여 사용하곤 한다. 
<em>check_permissions()</em> 는 위와 같이 설정한 권한 클래스들에 대해 권한이 있는지 모두 확인하는 매서드이다. </p>
<p>이 매서드에서 좀 전에 BasePermission에서 보았던 has_permission가 해당 permission을 통해 사용되고 있다❗️ </p>
<p>여기까지가 APIView에서 권한을 체크하는 전 과정이다. </p>
<blockquote>
<p>그러면 아직 사용되지 않은 has_object_permission는 언제 사용되는거지❓</p>
</blockquote>
<p>위에 있던 check_permissions() 매서드 바로 아래에 존재한다. </p>
<pre><code class="language-python">    #APIView
    def check_permissions(self, request):    
        # has_permission 호출
        ...

    def check_object_permissions(self, request, obj):
        &quot;&quot;&quot;
        Check if the request should be permitted for a given object.
        Raises an appropriate exception if the request is not permitted.
        &quot;&quot;&quot;
        for permission in self.get_permissions():
            if not permission.has_object_permission(request, self, obj):
                self.permission_denied(
                    request,
                    message=getattr(permission, &#39;message&#39;, None),
                    code=getattr(permission, &#39;code&#39;, None)
                )</code></pre>
<blockquote>
<p>차이점
✅ <strong>check_permissions</strong>
<code>➡️ has_permission 사용, 요청(request) 기반 권한 체크</code>
✅ <strong>check_object_permissions</strong>
<code>➡️ has_object_permission 사용, 객체(object) 기반 권한 체크</code></p>
</blockquote>
<p>결국 APIView에서는 자동으로 check_permissions를 호출하여 권한을 확인하지만 
check_object_permissions까지 체크하지는 않는다. 
(참고: GenericAPIView에서는 get_object()를 사용할때 체크함)</p>
<p>comment 객체에 대한 정보와 request의 정보 둘다 사용하여 권한을 체크해야하기 때문에 check_object_permissions를 사용해야했다. 
현재 APIView를 사용중이므로 이 매서드는 직접 호출해서 사용해서 체크해줘야한다.</p>
<h1 id="적용">적용</h1>
<p>위의 내용을 바탕으로 Custom Permission 클래스를 생성하고 APIView에 적용해보았다. </p>
<pre><code class="language-python">class IsAuthorOrOwner(permissions.BasePermission):
    &quot;&quot;&quot;
    삭제 권한
    게시글의 작성자 또는 댓글 작성자인경우 삭제 가능
    &quot;&quot;&quot;

    def has_permission(self, request, view):
        return request.method == &quot;DELETE&quot;  # 명시적으로 함

    def has_object_permission(self, request, view, obj):
        return any(
            [
               # 댓글 작성자 == 댓글 삭제 요청자
                obj.author and obj.author == request.user,  
                # 댓글을 작성한 게시글의 작성자 == 댓글 삭제 요청자
                obj.post and obj.post.author == request.user,
            ]
        )</code></pre>
<pre><code class="language-python">class CommentDetailAPIView(APIView):

    permission_classes = [IsOwnerOrReadOnly]  # 기본 권한 설정

    def get_permissions(self):
        if self.request.method == &quot;DELETE&quot;:
            return [IsAuthorOrOwner()]  # 삭제요청인 경우만
        return super().get_permissions()  # 위의 기본 권한 사용

       def get_object(self, id):
        comment = self.comment_service.get_comment_by_id(id)
        self.check_object_permissions(self.request, comment)
        return comment

    ...각 매서드들에서 get_object를 사용...</code></pre>
<p>HTTP DELETE MEHTOD를 사용할때 사용하여 댓글을 삭제할 경우에만 IsAuthorOrOwner 권한을 설정했다. </p>
<h3 id="마무리">마무리</h3>
<p>앞으로 여러 복잡한 권한이라도 직접 만들어서 사용할 수 있을거같다~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[programmers] 네트워크]]></title>
            <link>https://velog.io/@hwstar-1204/programmers-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@hwstar-1204/programmers-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Sun, 30 Mar 2025 07:08:28 GMT</pubDate>
            <description><![CDATA[<p>문제 출처 : <a href="https://school.programmers.co.kr/learn/courses/30/lessons/43162?language=python3">프로그래머스 네트워크</a></p>
<h1 id="문제-접근">문제 접근</h1>
<p>여러 컴퓨터들이 연결되어있는 인접행렬(computers)를 탐색하며 방문 표시
각 컴퓨터마다 탐색하여 같은 네트워크에 있는 컴퓨터를 모두 방문표시한다.
아직 방문하지 않은 컴퓨터에서 한번 탐색이 끝나면 하나의 네트워크를 찾은것이다.</p>
<p>같은 네트워크를 한번의 탐색으로 찾기 위해 DFS 그래프 탐색 알고리즘을 사용한다.</p>
<h2 id="재귀">재귀</h2>
<pre><code class="language-python">def DFS(computers, visited, now):
    visited[now] = True

    for i in range(n):
        if computers[now][i] and not visited[i] and i != now:
            DFS(n, computers, visited, i)


def solution(n, computers):
    networks = 0
    visited = [False] * n

    for i in range(n):
        if not visited[i]:
            DFS(computers, visited, i)
            networks += 1

    return networks</code></pre>
<h2 id="스택-list">스택 (List)</h2>
<pre><code class="language-python">def DFS(computers, visited, now):
    stack = [now]
    visited[now] = True

    while stack:
        now = stack.pop()
        for i, connected in enumerate(computers[now]):
            if connected and not visited[i]:
                visited[i] = True
                stack.append(i)

def solution(n, computers):
    networks = 0
    visited = [False] * n

    for i in range(n):
        if not visited[i]:
            DFS(computers, visited, i)
            networks += 1

    return networks</code></pre>
<h2 id="스택-deque">스택 (Deque)</h2>
<pre><code class="language-python">from collections import deque

def DFS(computers, visited, now):
    stack = deque([now])
    visited[now] = True

    while stack:
        now = stack.pop()
        for i, connected in enumerate(computers[now]):
            if connected and not visited[i]:
                visited[i] = True
                stack.append(i)

def solution(n, computers):
    networks = 0
    visited = [False] * n

    for i in range(n):
        if not visited[i]:
            DFS(computers, visited, i)
            networks += 1

    return networks</code></pre>
<blockquote>
<p>Stack을 만들때 List를 많이 사용하는데 어떤 블로그에서 List보다 Deque가 성능이 좋다고 사용했다고 해서 궁금했다. </p>
</blockquote>
<p>Deque는 양방향에서 삽입,삭제하는데 장점을 갖는 이중 연결 리스트로 구현되어있는 자료구조이다. 
어차피 stack은 끝쪽에서 삽입 삭제가 이루어져야하는 자료구조로 List를 사용하더라도 성능상 차이가 크지 않을것 같다. </p>
<p>[두 자료구조 모두 시간 복잡도: O(1)]</p>
<blockquote>
<p>그러면 삽입, 삭제시 <strong>메모리 관리 측면</strong>에서 두 자료구조의 차이점을 생각해 볼 수 있을것 같다. </p>
</blockquote>
<ul>
<li><p>List</p>
<ul>
<li>Python에서 리스트는 동적 배열로 배열 할당시 고정된 size의 연속된 메모리 공간을 할당하여 사용한다. </li>
<li>공간이 부족해지면 좀 더 큰 배열을 만들고 이전 배열을 복사해온다.
주어진 데이터의 공간보다 더 큰 공간을 차지하여 공간 낭비가 발생할 수 있다.</li>
</ul>
</li>
<li><p>Deque</p>
<ul>
<li>이중 연결 리스트이기 때문에 메모리의 공간이 연속적이지 않다. 
메모리 단편화 현상이 발생할 수 있다. (OS 메모리 할당 전략으로 보안)</li>
<li>block 크기만큼 만들어서 연결하고 해제를 하여 주어진 데이터 공간 만큼 공간을 차지한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>메모리 재할당 비용 측면에서 List보다 Deque가 더 효율적인것 같다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Brewbuds] 413 Error 트러블 슈팅 - 이미지 업로드 API]]></title>
            <link>https://velog.io/@hwstar-1204/413-Error-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%97%85%EB%A1%9C%EB%93%9C-API</link>
            <guid>https://velog.io/@hwstar-1204/413-Error-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%97%85%EB%A1%9C%EB%93%9C-API</guid>
            <pubDate>Wed, 05 Mar 2025 08:09:17 GMT</pubDate>
            <description><![CDATA[<h1 id="배경">배경</h1>
<p>사용하고 있는 s3의 용량이 크지 않기 때문에 관리 차원에서 사진을 업로드할 때 용량 제한을 할 필요가 있었다. </p>
<p><strong>고려 사항</strong></p>
<ul>
<li>최대 10개의 사진 업로드 가능 </li>
<li>사용하고 있는 s3용량 : 5GB</li>
<li>최소한의 사진 품질 </li>
</ul>
<p>소셜 플랫폼 특성상 사용자들이 게시글이나 시음기록을 작성할 때 사진을 첨부하는 경우가 꽤 있을것이다. </p>
<p>그러므로 각 사진의 최대 용량은 5MB으로 제한과 함께 백엔드 로직을 설정해 두었다. </p>
<h1 id="문제-상황">문제 상황</h1>
<p>front에서 개발중에 사진 업로드 테스트를 진행하였는데 413 에러가 발생하였다. 
<img src="https://velog.velcdn.com/images/hwstar-1204/post/c4a0488d-189e-46db-bdcc-95d24fdb9e38/image.png" alt=""></p>
<p>추가로 성공, 실패 케이스를 알려주셨다. </p>
<p><strong>성공 케이스</strong></p>
<ul>
<li>사진 7개, 각 용량: 100KB -&gt; 총 700KB</li>
</ul>
<p><strong>실패 케이스</strong> </p>
<ul>
<li>사진 7개 , 각 용량 : 1MB -&gt; 총 7MB </li>
<li>사진 7개, 각 용량 : 500KB -&gt; 총 3.5MB</li>
</ul>
<p>1MB가 넘지 않는 선에서는 api 호출하여 사진을 업로드하는데 문제가 없었다. </p>
<h1 id="해결-과정">해결 과정</h1>
<p><strong>413 Content Too Large</strong></p>
<blockquote>
<p>HTTP <strong><code>413 Content Too Large</code></strong> 응답 상태 코드는 요청 엔터티가 서버에 의해 정의된 제한보다 크다는 것을 나타냅니다.  <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Status/413">출처: MDN</a></p>
</blockquote>
<p>nginx를 proxy 서버로 설정해 놓은 상태에서 사진을 한번에 업로드할때 다른 개발자 분들도 413 에러를 겪은것을 찾아볼 수 있었다.   <a href="https://blog.leocat.kr/notes/2020/04/21/nginx-413-request-entity-too-large">참고 블로그</a></p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/5689f2a2-9113-4796-8143-fa17c438b617/image.png" alt=""></p>
<p>nginx.conf에서 client의 요청 크기를 제한 설정을 한 적이 없었지만 <a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size">nginx 공식문서</a>를 찾아본 결과 
기본 설정값은 1M으로 되어있기 떄문에 총 용량이 1M이 넘지 않는 케이스에서 성공한 것이었다. </p>
<pre><code class="language-bash">server {
    listen 80;

    location / {
        ...
        client_max_body_size 50M;
}</code></pre>
<p>최대 요청 크기를 50M (5MB * 10개)으로 
nginx.conf에서 http, server, location에 위의 Syntax에 맞게 설정하고
nginx를 reload한 후 테스트한 결과 모두 성공적으로 요청이 가능했다. </p>
<h2 id="추가-고민">추가 고민</h2>
<p>최악의 경우에 사용자가 5MB 10개 사진을 업로드 요청을 많이 하게 된다면 
(5MB * 10) * 100번 = 5GB 으로 사용중인 s3 저장용량이 꽉 차게된다. </p>
<p>5개의 사진으로만 한다고 가정해보아도 200번이면 용량이 꽉 찬다... </p>
<p><em>해결하기 위한 방법 고민</em></p>
<ol>
<li>s3 용량 증가. -&gt; 가장 간단하지만 비용 증가</li>
<li>각 사진 용량 제한 감소 -&gt; 사진의 품질이 떨어질 수 있다. </li>
<li>최대 사진 업로드 개수 감소 -&gt; 요구사항 변경 제안 필요,  사용자의 자유도 감소 </li>
</ol>
<p>아직 배포하기 전이기 때문에 배포 후 사용자들의 한 게시물 당 평균 사진 업로드 개수를 파악하고 결정하기로 하였다. </p>
<h1 id="느낀점">느낀점</h1>
<p>보통  api 오류가 있다면 백엔드 로직부터 확인해 보았다. 하지만 오류의 원인이 백엔드 로직에만 있다고 가정하지 않고 요청 흐름에 따라 하나씩 고민해보면 좋을것 같다고 느꼈다.
또한 nginx를 proxy서버로 사용중이지만 알지 못하고 사용되는 기능도 있는것을 알 수 있었고 좀 더 알아볼 필요가 있다고 느꼈다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BrewBuds] 회원 게시글 조회 쿼리 개선]]></title>
            <link>https://velog.io/@hwstar-1204/BrewBuds-%ED%9A%8C%EC%9B%90-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%A1%B0%ED%9A%8C-%EC%BF%BC%EB%A6%AC-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@hwstar-1204/BrewBuds-%ED%9A%8C%EC%9B%90-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%A1%B0%ED%9A%8C-%EC%BF%BC%EB%A6%AC-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Tue, 04 Feb 2025 08:00:51 GMT</pubDate>
            <description><![CDATA[<h2 id="회원-게시글-조회시">회원 게시글 조회시</h2>
<ol>
<li>팔로우 한 사람들의 게시글</li>
<li>팔로우 하지 않은 사람들의 게시글
순서대로 나와야하는 요구사항</li>
</ol>
<pre><code class="language-python"># 1 (팔로우한 사람들의 게시글)
following_posts = self.get_feed_by_follow_relation(user, True).filter(filters).order_by(&quot;-id&quot;)

# 2 (팔로우 하지 않은 사람들의 게시글)
unfollowing_posts = self.get_feed_by_follow_relation(user, False).filter(filters).order_by(&quot;-id&quot;)</code></pre>
<h3 id="현재">현재</h3>
<p>각각 쿼리(총 2번)해서 메모리에서 list, chain 으로 합치는 방법</p>
<p>Method: GET | Path: /records/post/ | Duration: 3.2465s | DB Queries: 16 | Status: 200</p>
<ul>
<li>cpu time : 3227.14ms</li>
<li>sql time : 285.54ms<pre><code class="language-python">posts = list(chain(following_posts, unfollowing_posts))</code></pre>
<h3 id="개선-방법">개선 방법</h3>
db에서 union으로 합치고 중복확인 하지않고 한번의 쿼리로 가져오는 방법</li>
</ul>
<p>Method: GET | Path: /records/post/ | Duration: 3.2377s | DB Queries: 15 | Status: 200</p>
<ul>
<li>cpu time : 3179.88ms</li>
<li>sql time : 281.22 ms<pre><code class="language-python">posts = following_posts.union(unfollowing_posts, all=True)</code></pre>
</br>
응답시간 차이는 얼마 나지 않는다. 
하지만 개선전에는 DB에 쿼리를 2번 실행하고 두 쿼리셋 데이터를 메모리에 모두 올려 어플리케이션 레벨에서 합치는 작업을 하고 
개선 후에는 쿼리 수를 1번으로 줄이고 DB 레벨에서 union으로 합쳐서 사용하여 메모리 사용량을 줄일 수 있었다. 

</li>
</ul>
<p>물론 쿼리 복잡도가 올라가 데이터의 크기가 크다면 DB 부하가 더 커질 여지가 있다.</p>
<h2 id="결론">결론</h2>
<ul>
<li>쿼리 수(2-&gt;1) 및 메모리 사용량을 감소</li>
<li>Python 레벨에서의 불필요한 chain() 연산을 제거함 -&gt; Union All로 해결</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django 패스워드 암호화 방법]]></title>
            <link>https://velog.io/@hwstar-1204/Django-%ED%8C%A8%EC%8A%A4%EC%9B%8C%EB%93%9C-%EC%95%94%ED%98%B8%ED%99%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@hwstar-1204/Django-%ED%8C%A8%EC%8A%A4%EC%9B%8C%EB%93%9C-%EC%95%94%ED%98%B8%ED%99%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 31 Jan 2025 11:41:22 GMT</pubDate>
            <description><![CDATA[<p>암호화 방법에 대해 공부를 해본 후 내가 사용하는 Framework에서는 
어떻게 패스워드를 암호화해서 사용하는지 궁금해서 찾아보았다. </p>
<p>password 필드를 가지고 있는 model을 찾아본 결과 <a href="https://github.com/django/django/blob/352d860b9107adbcde0f1fe5d0fce8e9090a51e4/django/contrib/auth/base_user.py#L43">AbstractBaseUser</a>에 존재한것을 볼 수 있다. </p>
<p>이 클래스에서 패스워드를 설정하는 매서드를 찾아 들어가 보았다. </p>
<ol>
<li>set_password()</li>
<li>make_password()</li>
<li>get_hasher()</li>
<li>PBKDF2PasswordHasher.encode()<h2 id="def-make_passwordpassword-saltnone-hasherdefault">def make_password(password, salt=None, hasher=&quot;default&quot;):</h2>
<img src="https://velog.velcdn.com/images/hwstar-1204/post/679ddaca-81fd-4608-84b1-a6bd29f24c0d/image.png" alt=""></li>
</ol>
<p><a href="https://github.com/django/django/blob/352d860b9107adbcde0f1fe5d0fce8e9090a51e4/django/contrib/auth/hashers.py#L94">github link</a></p>
<h3 id="flow">Flow</h3>
<ol>
<li>기본적인 유효성 검사 (password 입력값 존재여부, 타입 체크)</li>
<li>단방향 암호화 해시함수 설정</li>
<li>Salt 설정 or 생성</li>
<li>return 설정한 해시함수로 password encoding</li>
</ol>
<p><strong>Salt란?</strong> 
password와 함께 해시함수에 사용되어 암호화시켜 보안성을 높이는 역할을 하는 무작위성 데이터이다. 
(왜 사용되는지는 밑에서..)</p>
<h2 id="def-get_hasheralgorithmdefault">def get_hasher(algorithm=&quot;default&quot;):</h2>
<p>어떤 해시 알고리즘을 사용할지 결정하고 반환하는 매서드이다. 
이때 특정 알고리즘을 매개변수로 지정해주면 custom 하게 사용가능한거 같다. </p>
<p>그렇지 않고 &quot;default&quot; 기본으로 사용하면 settings.PASSWORD_HASHERS 에 있는 알고리즘 중 첫번째것을 사용하도록 되어있다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/22b9a9bc-031b-4554-8401-cc0aa01a2c1a/image.png" alt=""></p>
<p>django에서는 기본적으로 보안성이 높다고 평가되는 pbkdf2 알고리즘을 사용하고 있다.
<a href="https://github.com/django/django/blob/352d860b9107adbcde0f1fe5d0fce8e9090a51e4/django/conf/global_settings.py#L539">settings.PASSWORD_HASHERS</a></p>
<p>pbkdf2의 full name은 Password-Based Key Derivation Function 2 이다. 
비밀번호를 안전하게 저장하기 위해 설계된 키 파생 함수인데 주로 해싱 알고리즘과 함께 사용된다고 한다. </p>
<p>결론적으로 말하자면 해싱 알고리즘만 사용하여 비밀번호를 암호화해서 저장하는것은 보안상 취약하다. 
해싱은 단방향 알고리즘이지만 무차별 대입(brute force)기법으로 해킹하면 언젠가 알 수 있다. 
이런 무식한 방법은 단순하지만 막을 방법이 없기 때문에 최대한 해커가 정답을 찾는 시간을 매우 오래걸리게 하는 것이 필요하다. </p>
<p>(password가 영문 대소문자, 숫자 포함해서 만들었다고 치더라도 
해커가 이렇게 나올 수 있는 경우의 수를 모두 미리 계산해놓고 비교 연산만 해서 찾는다면??
이러한 방식을 Rainbow Table 이라고 부르는데 인터넷에 이미 계산해놓은 table이 있다고도 한다. )</p>
<p>pbkdf2가 더 안전한 키를 파생시키기 위해 이런 특징을 가지고 있다. </p>
<h3 id="pbkdf2-특징">pbkdf2 특징</h3>
<ul>
<li>해시 함수 사용 : SHA-256과 같은 해시 함수 기반으로 작동</li>
<li>반복 계산 : 해시 연산을 매우 많이 반복하여 brute force 공격 시간을 늘린다. </li>
<li>salt 사용 : 같은 비밀번호라도 추가 데이터로 인해 다른 해시 값으로 저장되어 Rainbow Table 공격을 방어한다.</li>
</ul>
<h2 id="class-pbkdf2passwordhasherbasepasswordhasher">class PBKDF2PasswordHasher(BasePasswordHasher)</h2>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/af055824-e232-49bd-b8d3-10519e0b3bd4/image.png" alt=""></p>
<p>해당 클래스는 BasePasswordHasher라는 추상 클래스 상속 받고 있다.  <a href="https://github.com/django/django/blob/352d860b9107adbcde0f1fe5d0fce8e9090a51e4/django/contrib/auth/hashers.py#L311">PBKDF2PasswordHasher</a>
(해당 추상 클래스는 인코딩, 디코딩, 검증 등등 필요한 매서드를 정의해 놓았다.)</p>
<p>(이 클래스에서 PBKDF2 방식은 recommend 라고  Docstring에 작성되어있는데
 MD5는 not recommended로 나와있다.. ㅋㅋ)</p>
<p>인코딩은 pbkdf2 , sha-256 해시 함수, 여러 반복횟수와 salt를 매개변수로 받아 실제 DB에 저장할 값을 만들어낸다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/880bfbc8-d56f-42b5-a0a5-07b9b2c91ea4/image.png" alt=""></p>
<p>실제로 DB에 저장된 예시를 보여주자면 이런식으로 저장되는데 </p>
<pre><code class="language-python">[&#39;pbkdf2_sha256&#39;, &#39;870000&#39;, &#39;jVopWKoyuCkbdgyIpCzg...&#39;, &#39;rRMmAfvhUCOP...&#39;]</code></pre>
<p>디코딩할 때는 &#39;$&#39;을 기준으로 3번 split하고 첫번째부터 algorithm, iterations, salt, hash 값을 의미한다. </p>
<p>이 정보를 이용해서 검증을 하도록 되어있다. </p>
<h2 id="결론">결론</h2>
<p>저번 포스팅의 단방향 암호화 기법중 해싱이 있었다. 이것을 이용해서 패스워드를 저장할때 사용한다
는 점에서 내가 사용중인 django 프레임워크는 어떻게 사용중인지에 대한 궁금증을 풀 수 있었다. 
또한 django framework의 내부 구현에 대해 직접 code를 찾아 따라가보면서 django 코드 구조도 엿볼 수 있었고 좀 익숙해지는 느낌이 든다. 
사실 django에서 너무 많은 기능을 제공하다보니 실제로 어떻게 작동하는지 보다는 어떻게 사용하는지에 초점을 두고 개발을 했었던거 같기도하다. (이런게 frameworker라는 건가?..) 
다음에도 이런 궁금증이 들면 직접 구현된 코드를 뜯어보며 공부해봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[암호화 종류]]></title>
            <link>https://velog.io/@hwstar-1204/%EC%95%94%ED%98%B8%ED%99%94-%EC%A2%85%EB%A5%98-uxzck6l3</link>
            <guid>https://velog.io/@hwstar-1204/%EC%95%94%ED%98%B8%ED%99%94-%EC%A2%85%EB%A5%98-uxzck6l3</guid>
            <pubDate>Fri, 24 Jan 2025 06:41:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/82ffbf6a-7d13-448d-86a0-cc4747b8fef6/image.jpeg" alt=""></p>
<p>암호를 만드는 알고리즘은 크게 2가지로 나눌 수 있다. </p>
<ul>
<li>단방향 : 평문을 암호화하면 복호화가 불가능한 알고리즘</li>
<li>양방향 : 평문을 암호화하고 복호화가 가능한 알고리즘 </li>
</ul>
<h1 id="단방향-암호화">단방향 암호화</h1>
<ul>
<li>단방향 알고리즘은 해싱(hashing)을 통해 암호화한다. </li>
<li>암호화가 필요하지만 복호화는 필요하지 않는 경우에 사용된다. </li>
<li>해시 알고리즘을 사용했을때 입력값이 동일하면 항상 동일한 해시 값을 반환한다. 
  데이터 무결성 검증 (비밀번호, 파일 변경 여부)
  password의 경우에는 사용자가 입력한 값을 암호화해서 서버에 저장해놓고 
  이후 사용자가 로그인하면 입력한 pw를 암호화한 값과 이미 암호화된 값을 비교하여 비교한다. </li>
</ul>
<p>ex) MD5, SHA-1, SHA-256, SHA-512
  MD5, SHA-1은 충돌(다른 입력값으로 같은 해시값을 생성하는 경우)이 있을 수 있는 알고리즘이다. </p>
<blockquote>
<p>현대에는 SHA-256, SHA-3, bcrypt, Argon2와 같은 더 안전한 알고리즘을 사용한다. </p>
</blockquote>
<h1 id="양방향-암호화">양방향 암호화</h1>
<ul>
<li>대칭키 : 암호화 Key == 복호화 Key</li>
<li>비대칭키 : 암호화 Key != 복호화 Key</li>
</ul>
<h2 id="대칭키">대칭키</h2>
<p>같은 Key로 암호화, 복호화를 진행하기 때문에 외부에 노출이 되면 안된다. </p>
<ul>
<li><p>비밀키 암호</p>
</li>
<li><p>장점 : 비대칭키에 비해 암/복호화가 빠르다. </p>
</li>
<li><p>단점 : 대칭키의 가장 큰 약점은 비밀키를 전송하는 과정에서 탈취될 가능성이 있다. </p>
</li>
<li><p>Block 방식
  문자열 단어 하나씩 블록으로 나누어 암호화</p>
<ul>
<li>feistel 구조 <ul>
<li>특정 계산 함수(라운드 함수)의 반복으로 암호화 
ex) DES, 3-DES, SEED</li>
</ul>
</li>
<li>SPN 구조<ul>
<li>여러 입력을 작게 나누고 대치 및 전치하는 과정을 반복하며 암호화
ex) AES, ARIA </li>
</ul>
</li>
</ul>
</li>
<li><p>Stream 방식
  비트 단위로 암호화</p>
<ul>
<li>속도가 빠르고 오류 전파 현상이 없어 오디오/비디오 스트리밍시 사용 됨
ex) RC-4</li>
</ul>
</li>
</ul>
<h2 id="비대칭키">비대칭키</h2>
<p>키 생성시 2개 생기는데 그 중 한개는 public key로써 공개가 되어야하고 다른 한개는 private key로써 공개되면 안된다. </p>
<ul>
<li>공개키 암호</li>
<li>장점 : 대칭키처럼 키를 공유하는 과정이 필요없다. </li>
<li>단점 : 대칭키에 비해 암/복호화가 느리다</li>
<li>ex) RSA(소인수분해), DH(이산대수), ECC(타원 곡선 방정식)</li>
</ul>
<blockquote>
<p>비대칭형 알고리즘으로 대칭형 암호키를 전송하고 실제 암호문은 대칭형 암호를 사용하는 식으로 상호보안적으로 사용한다고함</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[prefetch_related N+1 문제 해결]]></title>
            <link>https://velog.io/@hwstar-1204/prefetchrelated-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@hwstar-1204/prefetchrelated-N1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Fri, 13 Dec 2024 16:28:32 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-python">def get_record_detail(self, pk: int) -&gt; TastedRecord:
    &quot;&quot;&quot;기록 상세 조회&quot;&quot;&quot;
    return (
        TastedRecord.objects
        .select_related(&quot;author&quot;, &quot;bean&quot;, &quot;taste_review&quot;)
        .prefetch_related(
            Prefetch(&quot;photo_set&quot;, queryset=Photo.objects.only(&quot;photo_url&quot;)),
        )
        .get(pk=pk)
    )</code></pre>
<p>위와 같은 방식으로 기록물에 대해 작성자(사용자), 원두, 시음 리뷰 테이블은 왜래키로 참조하고 있었기 때문에 select_related를 통해 한번에 Join을 해놓은 상태이다. </p>
<p>해당 시음기록이 사진 테이블을 역참조 하고 있기 때문에 prefetch_related를 사용해서 메모리에서 결합하여 사용하도록 최적화를 하였다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/758ecec2-6397-468b-8ee2-5cb195fc067f/image.png" alt=""></p>
<p>그런데 로깅을 해보니 photo를 가져오는 부분에서 N+1 쿼리 문제가 발생하고 있었다. 
prefetch_related를 통해서 해당 게시물의 사진 url 만 가져오는것이 목적이었다. 
결과적으로 말하자면 Prefetch의 쿼리셋에서 only로 photo_url만 가져오도록 했던게 문제가 되었다. </p>
<hr>
<p>쿼리를 하나씩 살펴보면</p>
<ol>
<li>prefetch_related</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/29960126-fac1-4665-acf1-010096c6f7ba/image.png" alt=""></p>
<p>사진들을 역참조하여 데이터를 미리 가져오는 쿼리이다. 
이때 only 매서드에 적었던 photo_url과 photo의 id와 함께 가져온다. </p>
<ol start="2">
<li>n+1 쿼리 중 첫번째</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/df43a368-c5b9-4058-b2ec-fd47f6f6481d/image.png" alt=""></p>
<p>위에서  only(&quot;photo_url&quot;)는 id와 photo_url만 로드해왔다. 
하지만 ORM이 데이터를 결합하거나 객체를 초기화 하려면 기본적으로 필요한 추가 필드인 tasted_record_id (왜래키)가 없기 때문에 개별적으로 가져오기 위해 추가 쿼리를 실행하게 되었다.</p>
<p>TastedRecord의 photo_set은 Photo 테이블의 tasted_record_id 필드를 통해 역참조가 되고있다. 
즉 Photo 객체를 통해 TastedRecord와의 관계를 확인할려면 tasted_record_id가 필요한것이다.</p>
<p>이렇게 쿼리를 photo.id 만 다른 형태로 동일한 쿼리가 3번 실행되는 문제가 발생했다. </p>
<blockquote>
</blockquote>
<p>이는 only() 메서드를 사용하면서 발생한 <strong>지연 로딩(Lazy Loading)</strong> 문제로 인해, 필요한 필드를 가져오기 위해 매번 추가 쿼리를 실행한 것이 근본적인 원인이었다. </p>
<p>그렇다면 역참조 하고 있는 왜래키(tasted_record_id)로 only에서 같이 가져오도록 하면 N+1 문제를 해결할 수 있지 않을까? </p>
<pre><code class="language-python">Prefetch(
     &quot;photo_set&quot;, queryset=Photo.objects.only(&quot;photo_url&quot;, &quot;tasted_record_id&quot;)
 ),</code></pre>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/c92ba963-6e69-43db-b454-8496a527f937/image.png" alt=""></p>
<p>실행해본 결과 왜래키를 함께 가져오도록 하면서 문제가 해결된것을 볼 수 있었다. </p>
<br/>

<p>저렇게 필요한 필드만 가져오도록 하는것은 객체가 너무 많은 정보를 가지고 있을때 좀 더 적합한 방식이라고 생각한다. 
나에게 지금 상황에서는 해당 객체의 모든 필드를 가져오더라도 상관 없을 정도라고 생각했고 이와 같이 역참조 하도록 수정하였다. </p>
<pre><code class="language-python">def get_record_detail(self, pk: int) -&gt; TastedRecord:
    &quot;&quot;&quot;기록 상세 조회&quot;&quot;&quot;
    return (
        TastedRecord.objects
        .select_related(&quot;author&quot;, &quot;bean&quot;, &quot;taste_review&quot;)
        .prefetch_related(&quot;photo_set&quot;)
        .get(pk=pk)
    )</code></pre>
<p>(코드 가독성에서도 좀 더 편한거 같다 ㅎㅎ)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[의존성 패키지 관리 방법 ]]></title>
            <link>https://velog.io/@hwstar-1204/%EC%9D%98%EC%A1%B4%EC%84%B1-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@hwstar-1204/%EC%9D%98%EC%A1%B4%EC%84%B1-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B4%80%EB%A6%AC-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 06 Sep 2024 07:07:17 GMT</pubDate>
            <description><![CDATA[<p>라이브러리 설치시 해당 라이브러리의 의존성 패키지가 함께 설치된다. 
만약 어떤 의존성 패키지가 함께 설치되는지 확인하는 방법에 대해 알아보자 </p>
<h1 id="의존성-패키지-확인-방법">의존성 패키지 확인 방법</h1>
<ol>
<li>pip show 명령어</li>
</ol>
<p>라이브러리의 여러 정보중 requires 항목에서 볼 수 있다. </p>
<pre><code class="language-bash">pip show &quot;{library_name}&quot;

ex)
❯ pip show pre-commit
Name: pre-commit
Version: 3.8.0
Summary: A framework for managing and maintaining multi-language pre-commit hooks.
Home-page: https://github.com/pre-commit/pre-commit
Author: Anthony Sottile
Author-email: asottile@umich.edu
License: MIT
Location: &quot;location&quot;
**Requires: cfgv, identify, nodeenv, pyyaml, virtualenv**
Required-by: </code></pre>
<ol start="2">
<li>시각적으로 파이썬 의존성 트리를 보는 방법</li>
</ol>
<pre><code class="language-bash">pip install pipdeptree
pipdeptree --package &quot;{package_name}&quot;</code></pre>
<ol start="3">
<li>공식 github에서 파일로 확인하는 방법</li>
</ol>
<p>requirements.txt 이나 setup.py 파일을 확인할 수도 있다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/d1f2272e-4413-498f-b4d9-c7605d715786/image.png" alt=""></p>
<p>pip show에서 보았던 내용이 있고 아래에 install_requires에 의존성 목록을 볼 수 있다. </p>
<hr>
<p>이렇게 확인하고 라이브러리를 설치했다가 만약 해당 패키지만 삭제하면 같이 설치되었던 의존성들은 어떻게 될까?</p>
<p>pip uninstall ‘package_name’ 으로 패키지를 삭제하는데 해당 패키지만 삭제되고 의존성인 패키지는 그대로 남아 있는다. </p>
<p>왜냐하면 삭제된 패키지이외에 다른 패키지의 의존성일 수도 있기 때문에 같이 삭제했다가는 문제가 생길 수 있다. </p>
<p>그러면 처음에 설치했던 패키지가 더이상 필요없으면 해당 의존성을 찾아보고 일일이 다 삭제를 해야하나?? </p>
<p>매우 비효율적이라고 생각한다. </p>
<p><br><br></p>
<p>역시나 이러한 문제를 해결해주는 친절한 라이브러리가 존재했다. </p>
<p>pip-autoremove를 사용하면 특정 패키지를 삭제할 때 더 이상 다른 패키지에서 사용되지 않는 의존성 패키지를 자동으로 삭제해준다. </p>
<pre><code class="language-bash"># 설치
pip install pip-autoremove  

# 삭제 
pip-autoremove &quot;{package_name}&quot;
</code></pre>
<p>그런데 여러 패키지를 설치해서 실험하고 삭제하거나 프로젝트를 오래 진행하면필요하지 않는 패키지와 필요한 패키지가 requirements.txt에 섞여서 포함되어 있는 경우가 있을 수 있다. </p>
<p>이때 pip-check-reqs를 사용하면 requirements.txt에 포함되어 있지만 사용되지 않는 라이브러리를 쉽게 찾을 수 있다.</p>
<pre><code class="language-bash"># 설치
pip install pip-check-reqs

# 사용되지 않는 패키지 찾기 
pip-missing-reqs &quot;location&quot;</code></pre>
<p>실제 배포를 준비할때 프로젝트 내에서 실제로 사용되는 패키지들만 명시가 되어야 한다. </p>
<p>실제로 사용되는 라이브러리만 포함된 requirements.txt를 생성할 수도 있다. </p>
<p>pipreqs 라는 라이브러리를 사용하면 된다. </p>
<pre><code class="language-bash"># 설치 
pip install pipreqs

# 프로젝트 내에서 사용되는 새로운 requirements.txt 생성
pipreqs &quot;/path/to/your/project&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[CORS 오류]]></title>
            <link>https://velog.io/@hwstar-1204/CORS-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@hwstar-1204/CORS-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Tue, 20 Aug 2024 06:16:08 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/e82398fc-79eb-4ab9-b1ea-3ba5f9c50ede/image.png" alt=""></p>
<p>front에서 backend 서버로 요청을 보낼때 CORS 오류가 일어나는것을 접할 수 있다. </p>
<p>이 오류는 SOP와 CORS가 무엇인지 알면 해결방법을 쉽게 찾아 적용할 수 있다. </p>
<h1 id="sop와-cors-정책">SOP와 CORS 정책</h1>
<p>먼저 SOP와 CORS가 무엇인지 살펴보자 </p>
<ul>
<li>SOP : Same Origin Policy (동일 출처 정책)</li>
<li>CORS : Cross Origin Resources Sharing (교차 출처 리소스 공유)</li>
</ul>
<p>front에서 요청을 보내는것은 브라우저가 요청을 보낸것을 말한다. </p>
<p>이때 브라우저는 기본적으로 같은 출처간에 리소스를 공유하는것이 기본 원칙으로 되어있다. </p>
<p>하지만 front에서는 다른 출처의 리소스를 받아와서 보여줘야하는 일이 생기게 되는데 이때 SOP에 의해 차단되는것이다. </p>
<p>그러면 다른 출처의 리소스를 받아올 수 없는것일까?</p>
<p>이때 설정을 해줘야하는것이 CORS 이다. </p>
<blockquote>
<p>“ 원래는 같은 출처의 리소스를 공유하는것이 원칙이지만(SOP) 서버에서 CORS 설정으로 다른 출처의 리소스를 받아오는것을 허용한다면 가능하게 해줄게~ “</p>
</blockquote>
<h1 id="출처-판단-방법">출처 판단 방법</h1>
<p>두 정책에서 공통적으로 판단하는 ‘출처’는 어떻게 같고 다른지 아는걸까?</p>
<p>브라우저가 출처를 판단할때 보는것은 3가지이며 모두 같아야 같은 출처로 판단한다. </p>
<ul>
<li>스킴 (Scheme)</li>
<li>호스트 (Host)</li>
<li>포트 (Port)</li>
</ul>
<p>예를 들어 <a href="http://front-test:3000%EC%9D%98">http://front-test:3000의</a> 프론트 서버에서 </p>
<p><a href="http://backend-test:8000%EC%9D%98">http://backend-test:8000의</a> 백엔드 서버로 요청을 보낸다고 생각해보자 </p>
<p>이때 스킴은 http로 같지만 호스트와 포트가 다르기 때문에 출처가 다르다고 판단한다. </p>
<p>그렇다면 이것은 같은 출처일까? </p>
<ol>
<li><a href="http://localhost">http://localhost</a></li>
<li><a href="http://127.0.0.1">http://127.0.0.1</a></li>
</ol>
<p>결론적으로 말하자면 브라우저는 다른 출처로 판단하게된다.</p>
<p>두 주소의 스킴과 포트는 동일하지만 Host의 문자열이 서로 다르기 때문이라고 한다. </p>
<p>주소상 로컬호스트를 가리키지만 기술적으로 브라우저는 다른 출처로 인식하기 때문에 로컬 서버에서 개발할때 이 부분을 생각하고 설정을 해주어야한다. </p>
<h1 id="cors-설정-방법">CORS 설정 방법</h1>
<p>여러 백엔드 프레임워크 Spring, Django, Express에서 cors 설정하는 방법을 제시하고 있다. </p>
<p>그 중 Django에서 설정하는 방법을 알아보자</p>
<ol>
<li><strong>django-cors-headers 설치</strong></li>
</ol>
<pre><code class="language-bash">pip install django-cors-headers</code></pre>
<ol>
<li><strong>settings.py에 설정 추가</strong> </li>
</ol>
<pre><code class="language-python">INSTALLED_APPS = [
    &#39;corsheaders&#39;,
    ...
]

MIDDLEWARE = [
    &#39;corsheaders.middleware.CorsMiddleware&#39;,  # 상단에 위치하는것을 추천
    ...
]</code></pre>
<p>2-1 <strong>[필수] CORS 허용 출처 등록</strong> </p>
<ul>
<li>CORS_ALLOW_ALL_ORIGINS</li>
<li>CORS_ALLOWED_ORIGIN_REGEXES</li>
<li>CORS_ALLOW_ALL_ORIGINS</li>
</ul>
<p>3개 중 하나를 필수로 등록해야함</p>
<pre><code class="language-bash">CORS_ALLOWED_ORIGINS = [
    &quot;https://example.com&quot;,
    &quot;https://sub.example.com&quot;,
    &quot;http://localhost:8080&quot;,
    &quot;http://127.0.0.1:9000&quot;,
]

# 정규 표현식을 사용한 출처 등록 방법
CORS_ALLOWED_ORIGIN_REGEXES = [
    r&quot;^https://\w+\.example\.com$&quot;,
]
# https://로 시작하고, 그 뒤에 하나 이상의 문자로 이루어진 서브 도메인이 있으며,
# example.com으로 끝나는 출처에 대해 허용

CORS_ALLOW_ALL_ORIGINS = True  # 모든 출처 허용 
</code></pre>
<p>2-2 <strong>[선택] CORS 옵션</strong></p>
<p>옵션을 설정하지 않아도 기본값으로도 충분하지만 설정 할려면 할 수 있다. </p>
<ul>
<li><strong><code>CORS_URLS_REGEX</code></strong> : CORS 헤더가 전송될 URL을 제한하는 정규식 설정</li>
<li><strong><code>CORS_ALLOW_METHODS</code> :</strong> 요청에 허용되는 http 매서드 설정</li>
<li><strong><code>CORS_ALLOW_HEADERS</code> :</strong> 요청에 허용되는 http 헤더 설정</li>
</ul>
<p>등등 다른 옵션도 설정 가능하다.  더 자세한 내용은 참고자료의 django-cors-headers 깃헙에서 볼 수 있다. </p>
<h1 id="참고-자료">참고 자료</h1>
<p><a href="https://github.com/adamchainz/django-cors-headers">https://github.com/adamchainz/django-cors-headers</a>
<a href="https://developer.mozilla.org/ko/docs/Web/HTTP/CORS">https://developer.mozilla.org/ko/docs/Web/HTTP/CORS</a>
<a href="https://velog.io/@effirin/CORS%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80">https://velog.io/@effirin/CORS란-무엇인가</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[XML vs JSON]]></title>
            <link>https://velog.io/@hwstar-1204/XML-vs-JSON</link>
            <guid>https://velog.io/@hwstar-1204/XML-vs-JSON</guid>
            <pubDate>Fri, 16 Aug 2024 03:14:59 GMT</pubDate>
            <description><![CDATA[<p>다양한 애플리케이션, 플랫폼이나 시스템간에 통신하여 데이터를 교환할때 많이 사용되는 형식이 있다. </p>
<p>예전에는 XML 형식을 사용했지만 요새는 JSON 형식을 주로 사용하는것 같다. </p>
<p>먼저 시스템간 데이터 교환형식이 왜 필요한지 알아본 후 </p>
<p>두 형식의 차이점 , 장단점을 비교해보며</p>
<p>이 중에 왜 JSON이 주로 많이 사용되고 있는지에 대해 알아보고자 작성해보았다. </p>
<h1 id="데이터-교환-형식이-필요한-이유">데이터 교환 형식이 필요한 이유</h1>
<p>서로 다른 프로그래밍 언어와 플랫폼에서 실행되는 어플리케이션끼리 데이터를 주고받을때 표준화된 형식이 없다면 데이터를 전송받는 입장에서 일관되게 데이터를 파싱하기도 힘들고 데이터 교환시 호환성 오류가 날 가능성도 생기게된다.</p>
<p>개발자와 사용자가 어떤 시스템을 사용하는지에 따라 각 교환마다 데이터 형식과 구조를 문서화하고 이해해야하는 추가적인 노력도 들어갈것이다.  </p>
<p>또한 데이터의 크기가 커지면 속도가 저하될 수 있는 비효율성이 발생하거나 시스템간에 통합이 어려워서 상호 운용성이 떨어질 수 있다. </p>
<p>결국 데이터 교환의 신뢰성, 정확성이 떨어지게 되고 여러 시스템간에 데이터 처리와 통합이 복잡해지게된다. </p>
<p>ex) JAVA는 데이터 객체를 사용하고, Python은 딕셔너리를 사용해서 데이터를 표현하는데 두 어플리케이션간에 데이터를 교환하려면 교환 형식이 필요하다. </p>
<h3 id="표준화된-형식을-사용하게-되면-어떤-장점을-갖을까">표준화된 형식을 사용하게 되면 어떤 장점을 갖을까?</h3>
<p>위의 내용과 반대 내용이 주로 장점을 이룬다. </p>
<ol>
<li>다양한 시스템간에 호환성이 높아짐</li>
<li>데이터의 구조화가 명확해져 가독성이 높아지고 유지보수, 디버깅 및 테스트에 이점</li>
<li>웹 API를 제공하면 사용자는 쉽게 파싱해서 사용할 수 있게된다. </li>
<li>데이터 교환시 네트워크 대역폭을 절약하고 파싱 속도 증가 </li>
</ol>
<p>데이터를 전송하는 입장에서도 어떤식으로 해야 더 효율적으로 전송할 수 있을지 고민할 필요 없이 표준화된 데이터 교환 형식을 사용하여 전송하면 되기도 하다. </p>
<h1 id="xml-extensible-markup-langauge">XML (eXtensible Markup Langauge)</h1>
<p>💡 HTML의 한계점을 극복하기 위해 만들어졌으며, 데이터 저장, 전달 목적으로만 만들어졌다.
데이터의 구조와 의미를 정의하는 마크업 언어이며 데이터를 계층(트리)적으로 표현한다.</p>
<pre><code class="language-xml">&lt;guests&gt;
  &lt;guest&gt;
    &lt;firstName&gt;John&lt;/firstName&gt; &lt;lastName&gt;Doe&lt;/lastName&gt;
  &lt;/guest&gt;
  &lt;guest&gt;
    &lt;firstName&gt;María&lt;/firstName&gt; &lt;lastName&gt;García&lt;/lastName&gt;
  &lt;/guest&gt;
&lt;/guests&gt;</code></pre>
<p>구조 : XML 기본 문법, 태그, 속성 등</p>
<p>장점 </p>
<ul>
<li>서로 다른 플랫폼간 데이터 교환이 가능하다.</li>
<li>사용자 정의 태그를 만들어 사용 가능하여 확장성이 좋음</li>
</ul>
<p>단점</p>
<ul>
<li><p>태그가 많아서 복잡하며 용량이 늘어난다.</p>
<p>  → 응답시간과 파싱시간이 느려진다.</p>
</li>
</ul>
<h1 id="json-javascript-object-notation">JSON (JavaScript Object Notation)</h1>
<p>💡 XML의 비효율적인 용량과 호환성 및 가독성을 개선하기위해 나온 데이터 교환 형식
JavaScript 객체 표기법을 기반으로 한 데이터 형식으로 키-값 쌍으로 표현한다.</p>
<pre><code class="language-json">{&quot;guests&quot;:[
  { &quot;firstName&quot;:&quot;John&quot;, &quot;lastName&quot;:&quot;Doe&quot; },
  { &quot;firstName&quot;:&quot;María&quot;, &quot;lastName&quot;:&quot;García&quot; },
]}</code></pre>
<p>구조 : 객체와 배열, 키-값 쌍 등 </p>
<p>장점</p>
<ul>
<li>구문이 매우 간결하여 가독성이 좋다.</li>
<li>읽고 쓰기가 더 쉽고 파일의 크기가 작다.</li>
<li>JavaScript와의 호환성으로 쉽게 파싱이 가능하다.</li>
<li>다양한 프로그래밍 언어와 플랫폼에서 지원하여 웹 앱에서 사용하기 좋다.</li>
</ul>
<p>단점</p>
<ul>
<li>복잡한 데이터 유형은 지원하지 않는다. ( ex: 날짜, 사용자 정의 데이터 타입, 복잡한 구조)</li>
<li>XML에 비해 표준화가 부족함</li>
<li>UTF-8 인코딩만 지원한다.</li>
</ul>
<h1 id="json이-인기를-끄는-이유">JSON이 인기를 끄는 이유</h1>
<p>클라이언트, 서버모두 JavaScript를 사용하면서 JSON 직렬화 개체로 네이티브 매핑이 가능하다. (AJAX)</p>
<p>XML은 더 많은 데이터 유형을 지원하지만 그만큼 엄격함이 뒤따른다. </p>
<p>애자일 방법론에 적용할때 JSON은 XML에 비해 빠르게 작성이 가능하며 새로운 데이터 요소를 추가하는 등 유연함이 있어 애자일과 잘 어울린다. </p>
<br>

<p>그렇지만 XML도 아직 많이 사용되고 있다. </p>
<p>가장 큰 이유중 하나는 XML은 엄격하게 데이터 무결성을 검증하여 보장해주지만 JSON은 사용자가 직접 데이터 무결성을 검증해야한다.</p>
<p>그리고 JSON은 메타 데이터를 포함하지 않아서 큰 데이터를 읽을 때 데이터의 의미와 구조에 대해 이해하기 힘들어진다. 문서와 같은 대용량 파일들을 전송할때는 메타 데이터를 포함하는 XML을 사용하기도 한다. </p>
<p>JSON의 간결성과 효율성 덕분에 요즘 웹 애플리케이션에서는 많이 사용되지만, XML의 강력한 데이터 무결성과 구조적 특성 역시 여전히 유용하다. </p>
<h1 id="참고-자료">참고 자료</h1>
<p><a href="https://aws.amazon.com/ko/compare/the-difference-between-json-xml/">https://aws.amazon.com/ko/compare/the-difference-between-json-xml/</a>
<a href="https://ujeon.medium.com/xml-xml%EA%B3%BC-json%EC%9D%80-%EC%96%B4%EB%96%A4-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EC%9E%88%EB%82%98%EC%9A%94-a9e50141640">https://ujeon.medium.com/xml-xml과-json은-어떤-차이가-있나요-a9e50141640</a>
<a href="https://www.coovil.net/xml-vs-json/">https://www.coovil.net/xml-vs-json/</a>
<a href="https://12bme.tistory.com/202">https://12bme.tistory.com/202</a>
<a href="https://velog.io/@cil05265/XML%EA%B3%BC-JSON%EC%9D%98-%ED%8A%B9%EC%A7%95-%EA%B3%B5%ED%86%B5%EC%A0%90-%EC%B0%A8%EC%9D%B4%EC%A0%90">https://velog.io/@cil05265/XML과-JSON의-특징-공통점-차이점</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 객체의 속성 접근 방법]]></title>
            <link>https://velog.io/@hwstar-1204/Python-%EA%B0%9D%EC%B2%B4%EC%9D%98-%EC%86%8D%EC%84%B1-%EC%A0%91%EA%B7%BC-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@hwstar-1204/Python-%EA%B0%9D%EC%B2%B4%EC%9D%98-%EC%86%8D%EC%84%B1-%EC%A0%91%EA%B7%BC-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 06 Aug 2024 05:55:05 GMT</pubDate>
            <description><![CDATA[<p>파이썬에서 객체의 속성에 접근하는 방법에는 여러 가지가 있다.
다섯 가지 주요 방법에 대해 설명하고, 각 방법의 장단점과 사용 사례를 소개하려고한다.</p>
<h2 id="1-객체의-속성에-직접-접근">1. 객체의 속성에 직접 접근</h2>
<p>가장 간단한 방법으로, 객체의 속성에 직접 접근</p>
<h3 id="예제-코드">예제 코드</h3>
<pre><code class="language-python">class MyAge:
    def __init__(self, age):
        self._age = age

obj = MyAge(20)
print(obj._age)  # 출력: 20
obj._age = 30
print(obj._age)  # 출력: 30</code></pre>
<h3 id="장단점">장단점</h3>
<ul>
<li><strong>장점</strong>: 구현이 간단하고 직관적입니다.</li>
<li><strong>단점</strong>: 속성에 대한 캡슐화가 부족하여 외부에서 속성 값을 직접 변경할 수 있습니다.</li>
</ul>
<h3 id="사용-사례">사용 사례</h3>
<ul>
<li>간단한 클래스에서 내부 속성에 직접 접근하는 경우.</li>
</ul>
<hr>
<h2 id="2-객체-내에-getter-setter-메서드-직접-구현">2. 객체 내에 getter, setter 메서드 직접 구현</h2>
<p>getter와 setter 메서드를 직접 구현하여 속성 접근을 제어하는 방법</p>
<h3 id="예제-코드-1">예제 코드</h3>
<pre><code class="language-python">class MyAge:
    def __init__(self, age):
        self._age = age

    def get_age(self):
        return self._age

    def set_age(self, age):
        if age &lt; 0:
            raise ValueError(&quot;나이는 0보다 작을 수 없음&quot;)
        self._age = age

obj = MyAge(20)
print(obj.get_age())  # 출력: 20
obj.set_age(30)
print(obj.get_age())  # 출력: 30</code></pre>
<h3 id="장단점-1">장단점</h3>
<ul>
<li><strong>장점</strong>: 속성에 대한 캡슐화를 제공</li>
<li><strong>단점</strong>: 속성에 접근할 때마다 메서드를 호출해야한다. </li>
</ul>
<h3 id="사용-사례-1">사용 사례</h3>
<ul>
<li>속성 값을 설정할 때 검증 로직이 필요한 경우.</li>
</ul>
<hr>
<h2 id="3-property-사용">3. @property 사용</h2>
<p>@property 데코레이터를 사용하여 속성 접근을 메서드로 처리하되, 속성처럼 보이게 하는 방법</p>
<h3 id="예제-코드-2">예제 코드</h3>
<pre><code class="language-python">class MyAge:
    def __init__(self, age):
        self._age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        if age &lt; 0:
            raise ValueError(&quot;나이는 0보다 작을 수 없음&quot;)
        self._age = age

obj = MyAge(20)
print(obj.age)  # 출력: 20
obj.age = 30
print(obj.age)  # 출력: 30
# obj.age = -5  # ValueError: 나이는 0보다 작을 수 없음</code></pre>
<h3 id="장단점-2">장단점</h3>
<ul>
<li><strong>장점</strong>: 캡슐화와 사용의 편리함을 동시에 제공</li>
<li><strong>단점</strong>: 간단한 속성 접근 제어에는 적합하지만, 복잡한 로직에는 한계가 있을 수 있다.</li>
</ul>
<h3 id="사용-사례-2">사용 사례</h3>
<ul>
<li>속성 값을 읽고 쓰는 동작을 캡슐화하고, 속성처럼 접근하도록 만들고 싶은 경우.</li>
<li>기존 속성에 접근하는 코드를 변경하지않고, 해당 속성에 대한 검증 로직 추가와 같은 코드를 편리하게 작성 가능 </li>
</ul>
<hr>
<h2 id="4-디스크립터-사용">4. 디스크립터 사용</h2>
<p>디스크립터 클래스를 정의하고 이를 통해 속성 접근을 제어하는 방법</p>
<h3 id="예제-코드-3">예제 코드</h3>
<pre><code class="language-python">from weakref import WeakKeyDictionary

class Descriptor:
    def __init__(self):
        self.data = WeakKeyDictionary()

    def __get__(self, instance, instance_type):
        if instance is None:
            return self
        return self.data.get(instance, 0)

    def __set__(self, instance, value):
        if value &lt; 0:
            raise ValueError(f&quot;{self.name}는 0보다 작을 수 없음&quot;)
        self.data[instance] = value

class MyAge:
    age = Descriptor()
    height = Descriptor()

    def __init__(self, age, height):
        self.age = age
        self.height = height</code></pre>
<blockquote>
<ul>
<li>Descriptor 클래스는 <strong><strong>get</strong></strong>, <strong><strong>set</strong></strong> 모두 구현하였으므로 디스크립터로 판단된다.</li>
</ul>
</blockquote>
<ul>
<li>WeakKeyDictionary 사용이유 : 각각의 유일한 MyAge 인스턴스가 Descriptor 클래스를 공유하지 않으면서 메모리 누수를 막기위함 (일반 딕셔너리에서 강한 참조 X 약한 참조 O)</li>
</ul>
<h3 id="장단점-3">장단점</h3>
<ul>
<li><strong>장점</strong>: 복잡한 속성 제어 로직을 구현할 수 있으며, <strong>여러 클래스에서 재사용 가능</strong></li>
<li><strong>단점</strong>: 구현이 조금 복잡할 있음, 여러 클래스에서 재사용할 경우 하나의 디스크립터를 공유하지 않도록 주의 필요</li>
</ul>
<h3 id="사용-사례-3">사용 사례</h3>
<ul>
<li>속성 접근과 설정을 더욱 세밀하게 제어하고 싶은 경우.</li>
<li>여러 클래스에서 동일한 속성 접근 로직을 재사용할 때.</li>
</ul>
<hr>
<h2 id="5-__getattr__-__getattribute__-__setattr__-사용">5. <code>__getattr__</code>, <code>__getattribute__</code>, <code>__setattr__</code> 사용</h2>
<p>이 메서드들을 사용하여 속성 접근을 세밀하게 제어할 수 있습니다.</p>
<h3 id="예제-코드-4">예제 코드</h3>
<pre><code class="language-python">class MyAge:
    def __init__(self, age):
        self._age = age

    def __getattr__(self, name):
        return f&quot;{name} 속성은 존재하지 않습니다.
&quot;

    def __getattribute__(self, name):
        print(f&quot;{name} 속성에 접근 중 __getattribute__ 호출됨
&quot;)
        return super().__getattribute__(name)

    def __setattr__(self, name, value):
        print(f&quot;{name} 속성에 {value} 값을 설정 중
&quot;)
        self.__dict__[name] = value

obj = MyAge(10)  # _age 속성에 10 값을 설정 중
obj.age = 40     # age 속성에 40 값을 설정 중
print(obj.age)   # age 속성에 접근 중 __getattribute__ 호출됨 
 40
print(obj.non_existent_attr)  # non_existent_attr 속성에 접근 중 __getattribute__ 호출됨 
 non_existent_attr 속성은 존재하지 않습니다.</code></pre>
<h3 id="장단점-4">장단점</h3>
<ul>
<li><strong>장점</strong>: 모든 속성 접근을 세밀하게 제어할 수 있다. 
(속성에 접근할때, 존재하지 않는 속성에 접근할때, 값을 설정할때와 같이 해당 시기에 제어를 추가 가능)</li>
<li><strong>단점</strong>: 잘못 구현하면 무한 재귀 호출이 발생할 수 있다. 
(상위 클래스 object의 <strong><strong>getattribute</strong></strong> 호출로 해결 가능)</li>
</ul>
<h3 id="사용-사례-4">사용 사례</h3>
<ul>
<li>매우 세밀한 속성 접근 제어가 필요한 경우.</li>
<li>속성 접근 시 추가적인 로직(예: 로깅, 검증 등)을 구현하고 싶은 경우.</li>
</ul>
<hr>
<p>이 다섯 가지 방법을 통해 파이썬에서 객체의 속성 접근을 다양하게 제어할 수 있습니다.
각 방법의 장단점과 사용 사례를 잘 이해하면, 적절한 상황에 맞는 접근 방식을 선택할 수 있을 것입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Commit History 출력해보기]]></title>
            <link>https://velog.io/@hwstar-1204/2-Git-GitHub-%EC%BB%A4%EB%B0%8B-%ED%9E%88%EC%8A%A4%ED%86%A0%EB%A6%AC</link>
            <guid>https://velog.io/@hwstar-1204/2-Git-GitHub-%EC%BB%A4%EB%B0%8B-%ED%9E%88%EC%8A%A4%ED%86%A0%EB%A6%AC</guid>
            <pubDate>Tue, 16 Jul 2024 07:21:38 GMT</pubDate>
            <description><![CDATA[<p>앞에서 살펴본것과 같이 working directory 에서 여러 파일을 추가, 수정 하고 스테이징하여  staging area에 놓이게된다. 
여기에서 커밋을 하게되면 repository에 등록이 되면서 커밋 기록이 남게된다. </p>
<p>여러 작업을 하고 커밋을 해놓으면 기록들이 쌓이게 되는데 
이때 이전에 작업한 내용의 기록을 보고싶은 경우에 사용되는 명령어들을 소개하겠다. </p>
<blockquote>
<p>아래의 개념을 알면 이해하기 쉽겠지만 자세히 설명하지는 않겠다. 
HEAD, master, origin/master, origin/HEAD 개념 
HEAD, master, 커밋 해시 : 모두 특정 커밋을 직, 간접적으로 참조하고 있는 개체이다. </p>
</blockquote>
<p>➡️ 이를 통해 특정 커밋 기록을 보거나 비교하거나 변경 사항을 되돌리는 등 여러 명령어에서 사용할 수 있다.</p>
<h1 id="1-git-log">1. git log</h1>
<p>저장소에 기록된 커밋 히스트리를 출력하는 명령어 
시간역순으로 정렬하여 보여줌으로 최근 커밋 기록부터 상단에서 보여준다. </p>
<h3 id="git-log--출력할-커밋-수">git log -[출력할 커밋 수]</h3>
<p>: 최근 커밋 수를 지정하여 로그 출력
만약 모든 커밋 기록이 아니라 최근 몇 가지만 보고싶은 경우 옵션으로 지정할 수 있다. </p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/5f6fa03d-26b8-4677-b0fc-bac53989af6b/image.png" alt=""></p>
<p>ex) git log -1</p>
</blockquote>
<h3 id="git-log--p">git log -p</h3>
<p>: 각 로그의 상세 정보를 출력한다. git log --patch와 같은 명령어이다. </p>
<h3 id="git-log--p--출력할-커밋-수">git log -p -[출력할 커밋 수]</h3>
<p>: 최근 커밋 수를 지정하여 로그의 상세 정보 출력 
동일한 파일에서 코드 변경 사항(diff) 등 추가적인 상세 정보를 제공한다. </p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/6cb9875b-2cdc-484a-ae51-538b94f83e9a/image.png" alt=""></p>
<p>ex) git log -p -1</p>
</blockquote>
<h3 id="git-log---prettyoneline">git log --pretty=oneline</h3>
<p>: 커밋 히스토리를 한 줄로 정리되어 출력한다. 
여러 커밋의 내용을 커밋 메시지로 구분하여 한눈에 볼 때 유용하다. </p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/2045fb32-0e2c-4ba5-8a59-3c4020b50d65/image.png" alt=""></p>
</blockquote>
<h3 id="git-log---oneline">git log --oneline</h3>
<p>: 커밋 ID 값의 7번째 까지만 커밋 히스토리를 한줄로 출력
위와 다른점은 커밋 해시값의 일부만 출력하는 점만 다르다. </p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/41c9167e-dcfd-4ad5-9099-2ffa9b8dce23/image.png" alt=""></p>
</blockquote>
<h3 id="git-log---oneline---graph">git log --oneline --graph</h3>
<p>: 커밋 히스토리를 한줄로 그래프와 함께 출력</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/8be08a3b-a5da-4f89-91bd-837c696c2108/image.png" alt=""></p>
<p>ex) graph를 시각적으로 보여주기위해 &#39;devdev&#39;라는 branch를 만들어서 작업한 후 merge한 경우이다. </p>
</blockquote>
<h1 id="2-git-show">2. git show</h1>
<p>: 특정 커밋의 상세정보 출력 
여러 커밋 히스토리를 출력하는 git log -p 명령어와 특정 하나의 커밋 정보를 출력하는점에서 다르다. 
모든 커밋은 커밋 해시값을 가지고 있으므로 특정 커밋을 지정할 수 있다. </p>
<h3 id="git-show-커밋-해시">git show [커밋 해시]</h3>
<p>: 특정 커밋 해시에 대한 상세정보를 출력함</p>
<blockquote>
<p>&#39;devdev&#39; branch에서 작업한 내용을 출력해보았다.</p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/670f555b-2721-4bf0-9d09-ba48f7fb72fe/image.png" alt=""></p>
<p>ex) git show 9c82462 </p>
</blockquote>
<h3 id="git-show-head">git show HEAD</h3>
<p>: HEAD가 참조하는 커밋의 상세정보를 출력함
커밋 해시값을 항상 기억하기는 힘들수 있는데 이때 HEAD가 참조하고 있는 커밋의 상세정보를 출력 가능하다.</p>
<blockquote>
<p>위에서 graph를 출력한 결과에서 보면 HEAD가 가리키고 있는 커밋의 상세정보를 출력한 것이다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/3e1f4274-4915-4ccb-bf9d-7e5a2ea00625/image.png" alt=""></p>
</blockquote>
<p>HEAD를 기준으로 이전 커밋 기록을 출력하고 싶은 경우도 있을 수 있다. </p>
<blockquote>
<p>HEAD 바로 이전 커밋 기록의 상세 정보를 출력한다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/656cb77e-7347-48ee-95aa-e3588f470c02/image.png" alt=""></p>
<ul>
<li><strong>git show HEAD^^^</strong>
이렇게 HEAD 기준 3단계 이전의 기록을 출력도 가능하다. </li>
<li><strong>git show HEAD~n</strong>
HEAD를 기준으로 여러 이전 단계의 커밋 정보만 보고싶을때 사용할 수 있다. </li>
</ul>
</blockquote>
<h1 id="3-git-diff">3. git diff</h1>
<p>파일의 수정, 변경 사항을 git show 명령어 출력되는 비교보다 더 특화된 명령어이다. </p>
<h3 id="git-diff">git diff</h3>
<p>: Working Directory의 변경 사항과 최근 커밋의 내용을 비교</p>
<h3 id="git-diff---staged">git diff --staged</h3>
<p>: Staging Area의 내용과 최근 커밋 내용을 비교</p>
<h3 id="git-diff-변경-전-커밋-해시-변경-후-커밋-해시">git diff [변경 전 커밋 해시] [변경 후 커밋 해시]</h3>
<p>: 두 커밋 기록간의 변경 사항 비교</p>
<h1 id="느낀점">느낀점</h1>
<p>git에서 파일의 상세 정보와 변경 사항들을 명령어를 통해 확인 할 수 있었다. 
어떻게 커밋 기록을 저장하고 접근하며 비교하는지 알 수 있었다. </p>
<p>로그를 출력해보면서 작은 단위로 커밋을 하면서 커밋 메시지에 작업 내용을 간략하게 잘 적어놓으면 후에 나 혹은 협업자가 보았을때 작업 진행 상황을 쉽게 파악할 것이라고 생각했다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[programmers] 신고 결과 받기]]></title>
            <link>https://velog.io/@hwstar-1204/programmers-%EC%8B%A0%EA%B3%A0-%EA%B2%B0%EA%B3%BC-%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@hwstar-1204/programmers-%EC%8B%A0%EA%B3%A0-%EA%B2%B0%EA%B3%BC-%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Sun, 14 Jul 2024 06:11:42 GMT</pubDate>
            <description><![CDATA[<p>2022 KAKAO BLIND RECRUITMENT 문제</p>
<p>문제 링크 : <a href="https://school.programmers.co.kr/learn/courses/30/lessons/92334">https://school.programmers.co.kr/learn/courses/30/lessons/92334</a></p>
<h1 id="첫번째-idea">첫번째 Idea</h1>
<p>고유한 값에 대응하는 여러 값을 저장해 놓고 해결해야하는 문제로 보였다. </p>
<p>이러한 문제를 해결할때 key-value 구조를 가지는 자료구조인 해시테이블을 사용해봐야겠다고 생각했다. </p>
<p>파이썬의 딕셔너리는 해시테이블 기반 자료구조이고 key는 정수, 문자열, 튜플 등으로 가능하고 value는 여러가지 자료형을 지원한다. </p>
<p>여기에서 Key는 유저 ID를 저장하기 위해 문자열, value는 중복되지 않는 값을 저장해 놓을 수 있는 set를 사용해보았다. (하나의 유저가 여러번 신고한것은 1번 신고한것으로 처리하기 때문이다. ) </p>
<h1 id="코드">코드</h1>
<pre><code class="language-python">from collections import defaultdict

def solution(id_list, report, k):
        # 1
    reporter_dict = defaultdict(set)
    reported_dict = defaultdict(set)

    for record in report:
        r, d = record.split()
        reporter_dict[r].add(d)
        reported_dict[d].add(r)
    # 2
    stop_list = [id for id, ids in reported_dict.items() if len(ids) &gt;= k]

        # 3
    for reporter_id, ids in reporter_dict.items():
        cnt = 0
        for reported_id in ids:
            if reported_id in stop_list:
                cnt += 1
        reporter_dict[reporter_id] = cnt
    # 4 
    for i, id in enumerate(id_list):
        if id in reporter_dict.keys():
            id_list[i] = reporter_dict[id]
        else:
            id_list[i] = 0

    return id_list
</code></pre>
<h2 id="코드-흐름">코드 흐름</h2>
<ol>
<li>신고한사람을 key, 신고당한 사람을 value (reporter_dict), 신고당한사람을 key, 신고한 사람을 value (reported_dict)로 하는 두 개의 딕셔너리를 선언하고 저장 </li>
<li>k번 이상 신고받아 정지받을 사람의 id를 담은 리스트 생성 (stop_list)</li>
<li>유저가 신고한 사람 중 정지받은 사람이 있는지 확인하고 개수 업데이트 </li>
<li>id_list의 id 순서에 맞게 정답을 출력하기 위해 3번에 저장해놓은 개수를 id_list로 업데이트 </li>
</ol>
<h2 id="시간복잡도">시간복잡도</h2>
<p>1은 R (report의 개수)에 영향을 받는다. O(R) ( 1 ≤ report ≤ 200,000 )
2,4는 N (유저 ID 개수)에 영향을 받는다. O(N)  ( 2 ≤ id_list ≤ 1,000) </p>
<p>그러나 3번 부분에서 각 유저에 대해 평균적으로 신고한 사람의 수 M에 비례하다. 
최악의 경우에도 M &lt; N 이기 때문에 O( N * M )의 시간복잡도를 갖을 수 있다. </p>
<h1 id="두번째-idea">두번째 Idea</h1>
<p>신고한 사람과 신고당한 사람이 1:1로 대응되기 때문에 2차원 리스트로 할 수는 있겠다고 생각했다. 
대신 유저 ID를 인덱스로 매핑할때는 딕셔너리를 사용해야한다. </p>
<pre><code class="language-python">def solution(id_list, report, k):
        # 1
    length = len(id_list)
    answer = [0] * length
    key_mapping = {id: i for i, id in enumerate(id_list)}
    report_arr = [[0] * length for _ in range(length)]

    # 2
    for record in report:
        reporter, reported = map(lambda x: key_mapping[x], record.split())
        report_arr[reporter][reported] = 1

        # 3
    stop_list = []
    for i in range(length):
        cnt = 0
        for j in range(length):
            cnt += report_arr[j][i]
        if cnt &gt;= k:
            stop_list.append(i)

        # 4
    for i in range(length):
        for j in range(length):
            if report_arr[i][j] and j in stop_list:
                answer[i] += 1

    return answer</code></pre>
<p><strong>2차원 리스트</strong> (테스트 케이스)</p>
<table>
<thead>
<tr>
<th></th>
<th>muzi</th>
<th>frodo</th>
<th>apeach</th>
<th>neo</th>
</tr>
</thead>
<tbody><tr>
<td>muzi</td>
<td>0</td>
<td>1</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>frodo</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>apeach</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>neo</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
</tbody></table>
<p>muzi : 0, frodo : 1, apeach : 2, neo : 3</p>
<p>행 = 신고한 사람,  열 = 신고 당한 사람 </p>
<h2 id="코드-흐름-1">코드 흐름</h2>
<ol>
<li><p>ID를 인덱스로 매핑하고 2차원 리스트를 0으로 초기화한다. </p>
</li>
<li><p>신고한 사람과 당한 사람에 해당하는 2차원 리스트 공간에 1로 설정한다. </p>
<p> (1로 설정하는 이유 : 신고한 사람과 당한 사람은 1:1 이며, 한 사람의 중복 신고 처리가능하기 때문이다.)</p>
</li>
<li><p>신고를 K번 당한 사람을 알기 위해 2차원 리스트의 열의 합이 K이상인 사람의 인덱스를 저장한다. </p>
</li>
<li><p>각 사용자에 대해 자신이 신고한 사람들 중 정지된 사람의 수를 계산한다. </p>
</li>
</ol>
<h2 id="시간-복잡도">시간 복잡도</h2>
<p>2차원 리스트를 생성하고 3번, 4번 을 수행할 때 2차원 리스트를 모두 순회해야 하는 점에서 
시간복잡도가 O(N^2)가 나온다.  </p>
<p>만약 N의 크기가 커지고 한 사람당 신고한 사람 수의 평균이 적을 수록 불필요한 순회가 늘어나는 점에서 첫 번째 코드보다 시간 차이가 더욱 커질 것이다. </p>
<h1 id="두-가지-해결방법-비교">두 가지 해결방법 비교</h1>
<p><strong>첫번째</strong> </p>
<p>테스트 1 〉    통과 (0.02ms, 10.2MB)
테스트 2 〉    통과 (0.03ms, 10.3MB)
<strong>테스트 3 〉 통과 (1268.71ms, 63.7MB)</strong>
테스트 4 〉    통과 (0.04ms, 10.2MB)
테스트 5 〉    통과 (0.06ms, 10.1MB)
테스트 6 〉    통과 (1.22ms, 10.6MB)
테스트 7 〉    통과 (2.70ms, 11MB)
테스트 8 〉    통과 (4.96ms, 11.2MB)
테스트 9 〉    통과 (319.87ms, 35.5MB)
테스트 10 〉통과 (66.71ms, 35.5MB)
테스트 11 〉통과 (696.19ms, 63.6MB)
테스트 12 〉통과 (0.49ms, 10.4MB)
테스트 13 〉통과 (0.29ms, 10.3MB)
테스트 14 〉통과 (451.35ms, 29.5MB)
테스트 15 〉통과 (169.95ms, 52MB)
테스트 16 〉통과 (0.37ms, 10.3MB)
테스트 17 〉통과 (0.29ms, 10.3MB)
테스트 18 〉통과 (0.80ms, 10.1MB)
테스트 19 〉통과 (1.47ms, 10.4MB)
테스트 20 〉통과 (448.31ms, 29.4MB)
테스트 21 〉통과 (676.87ms, 51.9MB)
테스트 22 〉통과 (0.01ms, 10.1MB)
테스트 23 〉통과 (0.01ms, 10.4MB)
테스트 24 〉통과 (0.01ms, 10.2MB)</p>
<p><strong>두번째</strong></p>
<p>테스트 1 〉    통과 (0.02ms, 10.2MB)
테스트 2 〉    통과 (0.07ms, 10.4MB)
<strong>테스트 3 〉 통과 (1538.76ms, 32.6MB)</strong>
테스트 4 〉    통과 (0.07ms, 10.2MB)
테스트 5 〉    통과 (0.11ms, 10.2MB)
테스트 6 〉    통과 (3.08ms, 10.4MB)
테스트 7 〉    통과 (3.89ms, 10.6MB)
테스트 8 〉    통과 (7.17ms, 11MB)
테스트 9 〉    통과 (366.77ms, 19.3MB)
테스트 10 〉통과 (164.52ms, 19.4MB)
테스트 11 〉통과 (843.78ms, 32.6MB)
테스트 12 〉통과 (5.31ms, 10.4MB)
테스트 13 〉통과 (9.55ms, 10.5MB)
테스트 14 〉통과 (582.60ms, 25.2MB)
테스트 15 〉통과 (287.82ms, 32.7MB)
테스트 16 〉통과 (0.51ms, 10.4MB)
테스트 17 〉통과 (4.63ms, 10.4MB)
테스트 18 〉통과 (5.12ms, 10.3MB)
테스트 19 〉통과 (5.73ms, 10.5MB)
테스트 20 〉통과 (537.49ms, 25.3MB)
테스트 21 〉통과 (764.27ms, 32.8MB)
테스트 22 〉통과 (0.01ms, 10.2MB)
테스트 23 〉통과 (0.01ms, 10.3MB)
테스트 24 〉통과 (0.01ms, 10.2MB)</p>
<p>N의 크기가 크지 않은 경우에는 두가지 방법이 별로 차이가 나지 않는다. </p>
<p>N이 클 경우에는 첫번째는 최대 1268 ms, 두번째는 1538ms으로 차이가 난다. </p>
<p>그러므로 첫번째 방법이 더 효율적이라고 생각된다. </p>
<blockquote>
</blockquote>
<p>정답자들의 코드를 보았을때 첫번째 방법과 유사하지만 
좀 더 간결하며 신고 당한 사람을 key, 신고 당한 개수를 value로 하는 하나의 딕셔너리로 처리하였다.
이렇게 개선할 경우 공간 복잡도가 줄어들고 코드가 간결해지게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git이란?]]></title>
            <link>https://velog.io/@hwstar-1204/1-Git-GitHub</link>
            <guid>https://velog.io/@hwstar-1204/1-Git-GitHub</guid>
            <pubDate>Fri, 12 Jul 2024 14:47:08 GMT</pubDate>
            <description><![CDATA[<p>Git, GitHub는 팀 협업을 하면서 소스코드를 공유하고 버전을 관리하기 위해 필수적인 툴이라고 생각한다.
그동안 기본적이고 필수적인 요소만 알고 있었는데 한번 공부해 볼 필요가 있다고 생각하여 정리하고자 한다.</p>
<h1 id="git-이란">Git 이란?</h1>
<p>분산 버전 관리 시스템으로 소프트웨어 개발에서 소스 코드의 변경을 추적하는데 사용된다. </p>
<p>많은 개발자들이 팀프로젝트를 할때 관리하고 협업하는데 많이 사용되고 있다. </p>
<p>주로 로컬 컴퓨터에서 cmd를 통해 여러가지 기능을 사용한다. </p>
<h2 id="특징">특징</h2>
<ul>
<li>코드 버전 관리</li>
<li>브랜치 생성 및 병합</li>
<li>과거 버전으로 되돌리기</li>
<li>분산형 저장소 구조</li>
</ul>
<h1 id="git-동작-개념">Git 동작 개념</h1>
<p>Git이 어떻게 동작하는지 알기 위해서 작업 영역과 파일 상태에 대해 이해해야한다. </p>
<h2 id="git-작업-영역">Git 작업 영역</h2>
<p>작업한 파일들을 기록하고 저장하기 위해서 이러한 영역으로 나누어 놓았다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/76c30d62-c63a-4970-b0c5-fef576b5440e/image.png" alt=""></p>
<blockquote>
</blockquote>
<ul>
<li>*<em>Working Directory *</em>
  : 처음에 작업중인 파일이나 코드가 여기에 위치하여 추가, 수정하면 git이 변경사항을 자동으로 감지한다. </li>
<li><strong>Staging Area</strong>
  : working directory에서 기록하고싶은 파일을 add 명령어로 스테이징 했을 경우 파일의 위치     </li>
<li><strong>Repository</strong> (Local)
  : 스테이징된 파일들을 저장하기 위해 commit 명령어를 수행했을 경우 파일의 위치 </li>
</ul>
<h2 id="git-파일-상태">Git 파일 상태</h2>
<p>이러한 파일들의 상태를 확인하면 어떤 작업 영역에 위치하는지 알 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/226ac03e-428f-4ffd-af17-8da13c03c759/image.png" alt=""></p>
<blockquote>
</blockquote>
<ul>
<li><strong>Untracked</strong>
  : 기존에 관리하고 있지 않았던 파일이 새롭게 추가된 상태 </li>
<li><strong>Unmodified</strong>
  : 기존에 관리하고 있던 파일이지만 수정되지 않은 상태</li>
<li><strong>Modified</strong>
  : 기존에 관리하고 있던 파일이고 수정된 상태 </li>
<li><strong>Staged</strong>
  : 변경 사항을 기록하기 위해 add 명령어로 스테이징한 상태 </li>
<li><strong>Comitted</strong>
  : 변경 사항을 로컬 저장소에 저장히기 위해 commit 명령어로 커밋한 상태 </li>
</ul>
<h2 id="staging-area-용도">Staging Area 용도</h2>
<p>여기에서 중간 영역인 “Staging Area 없이 바로 Repository로 commit하면 되는거 아닌가?” 하는 의문이 들 수 있다. </p>
<p>이 영역이 필요한 이유는 여러모로 쓸모 있다. </p>
<ol>
<li><p>일부 파일만 커밋할 경우</p>
<p> 여러가지 파일을 수정했는데 한번에 커밋을 하여 기록하기보다 어떤 기준에 따라 커밋을 나눠서 하면 버전을 추적하기도 수월하고 추후 커밋 기록을 보았을 때 어떤 작업을 했는지 잘 알아볼 수 있다. </p>
</li>
<li><p>충돌을 수정할 경우
만약에 여러 사람들이 하나의 repository에서 작업을 할때 코드를 합치는 과정(merge)에서 충돌이 날 수 있는데 이때 충돌 나는 부분만 staging area에서 해결하여 커밋할 수 있다. </p>
</li>
<li><p>커밋을 수정할 경우</p>
<p> 커밋을 했는데 잘못 커밋을 했을 때 해당 커밋을 staging area으로 복구 시켜서 수정하고 다시 커밋 할 수 있다. </p>
</li>
</ol>
<h1 id="명령어-사용에-따른-저장소-변화">명령어 사용에 따른 저장소 변화</h1>
<p>git status는 저장소의 상태를 확인하는 명령어이다. 이 명령어를 통해 저장소의 상태를 단계별로 살펴보자 </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/09520d1f-f9c1-45d3-a51f-006d180a83bf/image.png" alt=""></p>
<p>아무 변경사항이 없는 초기 저장소의 상태이다. ( 현재 작업 위치: <code>Working Directory</code> )</p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/cc36e285-30af-4d67-9046-b5c30b23de50/image.png" alt=""></p>
<p>main.py 파일을 수정한 후 저장소의 상태이다. 파일의 수정을 git이 감지하여 파일의 상태가 Unmodified -&gt; Modified 상태로 바뀐 모습을 볼 수 있다. 
하지만 아직 commit을 위해 staging area에 추가되지 않았음을 알려준다. 
친절하게 스테이징 하는 명령어 add 사용법을 알려주고 
내가 파일에 수정한 내용을 삭제하고 원본 working directory로 되돌리는 명령어 restore 사용법을 알려준다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/dc91a540-023b-42e3-a9e3-c4ce937d5083/image.png" alt=""></p>
<p>add 명령어로 main.py의 변경사항을 스테이징 하였다. ( 현재 작업 위치: <code>Staging Area</code> )
친절하게 스테이징 영역에서 working directory로 옮길때는 restore 명령어에 --staged 옵션으로 하라고 알려준다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/13c641a5-8385-4268-aeb8-5779859bf573/image.png" alt="">
commit 명령어로 스테이징 영역에서 커밋하였다. ( 현재 작업 위치: <code>Local Repository</code> )
-m 옵션을 통해 바로 커밋 메시지를 작성할 수 있다. </p>
<p>커밋을 하게되면 기록마다 해시값이 지정이된다. 추후 개발을 하다가 해당 커밋한 위치로 돌아오거나 파일을 비교하거나 할 때 필요한 놈이다. 
이 해시값은 고유하므로 다른 개발자도 이 해시값만 알면 해당 커밋의 내용을 볼 수는 있다. (대부분은 커밋 메시지로 구분하겠지만)
[master e292aaa] : 현재 master 브랜치에서 커밋한 기록의 해시값은 &#39;e292aaa&#39; 이라고 알려준다. </p>
<h2 id="command-tip">Command Tip</h2>
<pre><code class="language-bash">git add .     # working directory에 추가,수정된 모든 파일을 한번에 스테이징 가능
git commit -am &quot;commit msg&quot;        # add와 commit을 한꺼번에 명령 가능  
git restore &quot;filename&quot;        # working directory에서 수정한 내역 삭제 
git restore --staged &quot;filename&quot;        # staging area -&gt; working directory</code></pre>
<h1 id="느낀점">느낀점</h1>
<p>git으로 로컬 환경에서 여러 작업 공간을 두고 CLI 기반으로 파일을 관리하는 방법을 알 수 있었다. 
github desktop으로 간단하게 하던 작업들이 어떻게 작동하는지 자세히 알 수 있었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python Dictionary 내부 구조  (+HashTable)]]></title>
            <link>https://velog.io/@hwstar-1204/Python-Dictionary-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@hwstar-1204/Python-Dictionary-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sat, 06 Jul 2024 14:33:37 GMT</pubDate>
            <description><![CDATA[<p>Python 프로그래밍에서 가장 자주 사용되는 자료 구조 중 하나는 바로 딕셔너리이다. 
딕셔너리는 키-값 쌍을 저장하고, 키를 이용해 빠르게 값을 검색할 수 있다. </p>
<p>이러한 딕셔너리의 효율성은 <strong>해시 테이블</strong>이라는 강력한 자료 구조 덕분이다. </p>
<h2 id="해시-테이블이란">해시 테이블이란?</h2>
<p>해시 테이블은 데이터의 저장과 검색을 빠르게 처리하기 위해 사용되는 자료 구조이다.
해시 테이블은 키를 해시 함수에 입력하여 고유한 해시 값을 생성하고, 이 해시 값을 이용해 데이터를 배열의 특정 위치에 저장한다.</p>
<h1 id="python-딕셔너리의-구조">Python 딕셔너리의 구조</h1>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/fd52c4e2-58af-4593-97f3-c293cb8aa830/image.png" alt=""></p>
<p>Python 딕셔너리는 내부적으로 배열과 연결된 여러 개의 버킷(bucket)으로 구성된다.
각 버킷은 특정 해시 값을 가지며, 이 해시 값은 키를 해시 함수에 적용한 결과이다.</p>
<p>초기 크기는 8개의 버킷으로 초기화 된다. </p>
<h1 id="충돌-처리">충돌 처리</h1>
<p>해시 테이블에서는 서로 다른 두 키가 같은 해시 값을 가질 때 충돌이 발생하는데</p>
<p>Python 딕셔너리는 충돌을 해결하기 위해 <strong>개방 주소법(open addressing)</strong>을 사용한다. 
개방 주소법은 충돌이 발생한 경우 해시 테이블 내에서 다른 빈 버킷을 찾아 저장하는 방법이다. </p>
<p>해시 테이블 내에서 다른 빈 버킷을 찾는 과정을 ‘프로빙(probing)’이라고 한다. 
Python에서는 빈 버킷을 찾기 위해서 ‘<em>Perturbation Shift Probing</em>’ 이라는 고유 알고리즘을 사용한다. </p>
<p>간단하게 말하면 mod 연산 + 비트 연산으로 다이나믹하게 이동하면서 다음 빈 버킷의 위치를 찾는다. </p>
<p>(체이닝(chaining) 충돌 처리 기법을 사용하지 않은 이유는 링크드 리스트를 만들때 큰 오버헤드가 요구되기 때문이라고 적혀있긴하다.. 파이썬은 모든게 객체로 이루어져 있어서 그럴만도 하다. 여담으로 JAVA는 체이닝 기법을 사용한다.) </p>
<h2 id="데이터-삽입과-검색">데이터 삽입과 검색</h2>
<ul>
<li><strong>삽입</strong>: 키-값 쌍을 삽입할 때 해시 값을 계산하여 인덱스를 찾고, 해당 인덱스의 버킷이 비어 있으면 키-값 쌍을 저장한다. 비어 있지 않으면 프로빙 하여 빈 버킷에 저장한다.</li>
<li><strong>검색</strong>: 키로 값을 검색할 때 해시 값을 계산하여 인덱스를 찾고, 해당 인덱스의 버킷에 키가 같으면 값을 반환합니다. 키가 다르면 다음 버킷을 확인한다.</li>
</ul>
<h2 id="동적-크기-조정">동적 크기 조정</h2>
<p>개방 주소법을 충돌 처리 방법으로 하면서 해시 테이블의 크기를 유연하게 늘리지 못하는 단점이 있었다. 
이때 해시 테이블에 일정 수준 이상으로 차게되면 성능이 급격이 저하되는 문제가 발생한다. </p>
<p>Python에서는 이러한 문제를 해결하기 위해 버킷의 2/3을 초과하여 채워지면, 새로운 더 큰 배열을 할당하고 기존 데이터를 새 배열로 재배치한다. </p>
<p>이렇게 재해싱과정을 거치게 되면 버킷이 많아져서 해시 충돌의 가능성이 줄어들게 된다. 
충돌이 적어지면 해시 테이블의 속도가 개선될 수 있는것이다. </p>
<h2 id="시간-복잡도">시간 복잡도</h2>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/9cf38302-43e5-4cc3-8023-e00317db268d/image.png" alt=""></p>
<p><a href="https://wiki.python.org/moin/TimeComplexity">공식 문서</a>에 나와있는 표이다. 
최악의 시간 복잡도는 해시 함수가 key를 고르게 분배하지 못했을 경우이다. </p>
<h3 id="결론">결론</h3>
<p>Python 딕셔너리는 해시 테이블을 사용하여 키-값 쌍을 효율적으로 관리하는 데이터 구조이다. 
데이터의 빠른 검색과 삽입을 가능하게 하기 위해 Python에서는 어떤 방식을 채택하여 내부적으로 처리하는지 알 수 있었다. </p>
<p>참고 자료 </p>
<p><a href="https://neos518.tistory.com/225">https://neos518.tistory.com/225</a>
<a href="https://fierycoding.tistory.com/68">https://fierycoding.tistory.com/68</a>
<a href="https://stackoverflow.com/questions/327311/how-are-pythons-built-in-dictionaries-implemented/44509302#44509302">https://stackoverflow.com/questions/327311/how-are-pythons-built-in-dictionaries-implemented/44509302#44509302</a>
<a href="https://medium.datadriveninvestor.com/internal-implementation-of-dictionary-in-python-5b739d5535a4">https://medium.datadriveninvestor.com/internal-implementation-of-dictionary-in-python-5b739d5535a4</a>
<a href="https://tenthousandmeters.com/blog/python-behind-the-scenes-10-how-python-dictionaries-work/">https://tenthousandmeters.com/blog/python-behind-the-scenes-10-how-python-dictionaries-work/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hash Table]]></title>
            <link>https://velog.io/@hwstar-1204/Hash-Table</link>
            <guid>https://velog.io/@hwstar-1204/Hash-Table</guid>
            <pubDate>Fri, 05 Jul 2024 09:10:18 GMT</pubDate>
            <description><![CDATA[<p>파이썬의 dictionary는  Hash Table을 이용하여 구현된 자료구조이다. </p>
<blockquote>
<p>그러면 Hash Table 자료구조는 어떻게 만들어질까?</p>
</blockquote>
<p>먼저 해시(Hash)에 대해 알아야한다. </p>
<h1 id="해시hash란">해시(Hash)란?</h1>
<p>해시는 임의의 길이를 가진 데이터를 고정된 길이를 가진 데이터로 매핑한 것이다. 
원본 데이터를 키(Key), 매핑 하는 과정을 해싱(Hashing), 결괏값은 해시 값이라고 한다. </p>
<p><strong>특징</strong></p>
<ul>
<li>고정된 길이</li>
<li>해시 값으로부터 key를 역산할 수 없음</li>
<li>key가 다르면 해시 값도 달라야 함</li>
</ul>
<p>key를 해시 값으로 매핑하기 위한 방법이 해시 함수이다.</p>
<h1 id="해시-함수란">해시 함수란?</h1>
<p>임의의 길이를 갖는 데이터(key)에 대해 고정된 길이의 데이터(index==해시값)를 출력하는 함수 </p>
<p><strong>특징</strong></p>
<ul>
<li>해시 값을 고속으로 계산 가능</li>
<li>입력 데이터의 크기와 관계없이 항상 동일한 길이의 해시 값을 출력함</li>
<li>동일한 입력에 대해 항상 같은 해시 값을 출력함</li>
</ul>
<p><strong>해시 함수의 종류</strong> </p>
<ul>
<li>나눗셈법<ul>
<li>key를 테이블의 크기로 나눈 나머지를 해시 값으로 사용한다.</li>
</ul>
</li>
<li>곱셈법<ul>
<li>키에 특정 상수 A(0 &lt; A &lt; 1)를 곱한 후 소수 부분을 테이블 크기를 곱해서 해시값을 생성한다.</li>
</ul>
</li>
</ul>
<p>(간단하게 2개만 알아보았지만 더 많이 있다. 더 알아보고싶다면 <a href="https://www.geeksforgeeks.org/hash-functions-and-list-types-of-hash-functions/">여기</a>를 참조하자)</p>
<p>위의 해시 함수는 key가 숫자일때 함수의 입력값으로 사용가능한데 key가 문자열인 경우에는 문자를 숫자로 변환하고(ASCII) 이 숫자들을 다항식의 값으로 변환하여 사용하는 ‘문자열 해싱’을 통해서 key로 지정한다. </p>
<h1 id="해시-테이블이란">해시 테이블이란?</h1>
<p>: 해시 함수를 사용해서 키를 해시값으로 매핑하고 이 고유한 인덱스 주소값에 key-value를 저장하는 자료구조
<img src="https://velog.velcdn.com/images/hwstar-1204/post/1e10ba2e-c41e-4274-9859-2bd4d1c5cc52/image.svg" alt=""></p>
<p>hash table은 bucket 배열로 생성된다.
bucket은 인덱스 주소값 위치에 key-value가 저장되는 곳이다.  </p>
<p>해시 함수의 목표는 배열에서 키를 균등하게 분배하는것이다. (좋은 해시 함수일 수록 충돌 횟수를 최소화 시킨다.) 
그러나 해시 함수에서 서로 다른 입력값에 대해 해시 함수의 결괏값이 같을 수도 있는데 이것을 <strong>해시 충돌</strong>이라고 부른다.</p>
<p>테이블의 크기가 5 이고 해시 함수는 나눗셈 법을 이용하여 해시 충돌 예시를 들어보겠다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/f6aa18f3-0e25-4b9f-a0f9-35ad2ddf7761/image.png" alt=""></p>
<ol>
<li>key-value (5,A)를 저장<ul>
<li>key값 5는 테이블의 크기로 나눈 나머지값 (5 % 5) 0을 인덱스로 하여 A를 저장한다. </li>
</ul>
</li>
<li>key-value (6,B)를 저장<ul>
<li>위와 같은 방법으로 (6 % 5) 1을 인덱스로하여 B를 저장한다. </li>
</ul>
</li>
<li>key-value (12,C)를 저장 <ul>
<li>위와 같은 방법으로 (12 % 5) 2를 인덱스로 C를 저장한다. </li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/4de3a3ca-6923-48bf-98bf-d7428cac592b/image.png" alt=""></p>
<ol start="4">
<li>key-value (32, D)를 저장<ul>
<li>위와 같은 방법으로 인덱스를 계산하면 (32 % 5 = 2)  3번과 인덱스가 겹친다. </li>
</ul>
</li>
</ol>
<p>→ 두 개 이상의 키가 동일한 인덱스에 매핑되어 해시 충돌이 일어나는 문제가 있다.  </p>
<h2 id="해시-충돌-해결방법">해시 충돌 해결방법</h2>
<ol>
<li>체이닝 (Separate Chaining)
<img src="https://velog.velcdn.com/images/hwstar-1204/post/e0d56ba9-2571-4983-a465-041dfa001d4a/image.png" alt=""></li>
</ol>
<p>한 bucket에 들어갈 수 있는 엔트리 수에 제한을 두지 않는 방법으로 충돌하는 key-value를 저장하기 위해 연결리스트 또는 다른 적합한 데이터 구조(연결리스트,트리,동적배열 등)를 이용하여 체이닝 하는 방법</p>
<ul>
<li>장점<ul>
<li>유연한 저장 구조 : 충돌하는 요소의 수 만큼 동적으로 조절 가능하다.</li>
<li>효율적인 삽입 및 삭제 : 새로운 값을 추가 삭제가 효율적이고 연결된 요소가 많지 않을 경우 탐색의 평균 시간 복잡도는 O(1)이다.</li>
</ul>
</li>
<li>단점<ul>
<li>추가 메모리 비용 : 추가적인 데이터 구조를 이용하여 해결하는 방법이다.</li>
<li>포인터 추적 : 요소가 많아지면 포인터를 따라가야 하므로 추가적인 시간이 필요해진다.
( linked list의 단점을 생각해보면 된다. )</li>
<li>캐시 성능 : 데이터가 메모리 전체에 분산되어있어 상대적으로 캐시 성능이 떨어질 수 있다.</li>
</ul>
</li>
</ul>
<ol start="2">
<li>개방 주소법 (Open Addressing)
<img src="https://velog.velcdn.com/images/hwstar-1204/post/fb47fd92-0e61-4c6b-8196-1b9dcb56c9d3/image.png" alt=""></li>
</ol>
<p>한 bucket에 들어갈 수 있는 엔트리가 하나뿐인 방법으로 해시 태이블 배열의 빈 공간을 사용하는 방법 
선형 탐색, 2차 탐색, 이중 해싱 등 다양한 방법이 있다. 
선형 탐색은 해당 충돌 인덱스부터 사용되지 않는 곳을 찾을 때 까지 순차적으로 내려가며 탐색한다. (그림 예시) </p>
<ul>
<li>장점<ul>
<li>메모리 사용 효율성 : 추가적인 데이터 구조가 필요하지 않다.</li>
<li>캐시 성능 : 모든 데이터가 동일한 테이블에 저장되어 있다.</li>
</ul>
</li>
<li>단점<ul>
<li>테이블의 크기 제한: 테이블이 가득 차면 더 이상 요소를 추가할 수 없다.</li>
<li>클러스터링 : 해시 테이블의 특정 영역에 데이터가 집중되는 현상으로 검색, 삽입, 삭제 연산의 성능이 저하될 수 있다. 특히 새로운 데이터를 삽입할 때 연쇄적인 충돌이 발생할 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="시간-복잡도">시간 복잡도</h2>
<table>
<thead>
<tr>
<th>연산</th>
<th>평균</th>
<th>최악</th>
</tr>
</thead>
<tbody><tr>
<td>삽입</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
<tr>
<td>삭제</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
<tr>
<td>검색</td>
<td>O(1)</td>
<td>O(n)</td>
</tr>
</tbody></table>
<p>해시 함수의 성능, 초기 테이블의 크기, 충돌 방법이 시간복잡도에 영향을 줄 수 있다. 
해시 함수가 키를 고르게 분산시키지 못하면 충돌이 많이 나서 O(n)의 시간 복잡도를 보여준다. </p>
<h2 id="느낀점">느낀점</h2>
<p>해시 함수와 해시 테이블에 대해 깊이 이해할 수 있었다. 
해시 테이블에서 해시함수를 사용할 때 해시 함수를 이용해서 key-value 쌍을 관리하는데 고유한 key에 대한 index값이 충돌할 수 있는 문제가 있다. 충돌 가능성이 있지만 이를 해결하는 방법에 대해 알게되었고 각각의 장단점을 비교해 볼 수 있었다. </p>
<p>다음에는 파이썬의 딕셔너리는 어떤 방식으로 해시 테이블을 이용해서 구현되었는지 알아보아야겠다. 
-&gt; <a href="https://velog.io/@hwstar-1204/Python-Dictionary-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0">Python Dictionary 내부 구조 (+hashtable)</a></p>
<h3 id="참고-자료">참고 자료</h3>
<p><a href="https://mathcenter.oxford.emory.edu/site/cs171/collisionResolution/">https://mathcenter.oxford.emory.edu/site/cs171/collisionResolution/</a>
<a href="https://medium.com/@MakeComputerScienceGreatAgain/separate-chaining-a-hashtables-collision-resolution-technique-7d94e96393e3">https://medium.com/@MakeComputerScienceGreatAgain/separate-chaining-a-hashtables-collision-resolution-technique-7d94e96393e3</a>
<a href="https://www.algolist.net/Data_structures/Hash_table/Open_addressing">https://www.algolist.net/Data_structures/Hash_table/Open_addressing</a>
<a href="https://mangkyu.tistory.com/102">https://mangkyu.tistory.com/102</a>
<a href="https://velog.io/@taeha7b/datastructure-hashtable">https://velog.io/@taeha7b/datastructure-hashtable</a>
<a href="https://growth-msleeffice.tistory.com/93">https://growth-msleeffice.tistory.com/93</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python List VS Array]]></title>
            <link>https://velog.io/@hwstar-1204/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%98-%EB%B0%B0%EC%97%B4%EA%B3%BC-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@hwstar-1204/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9D%98-%EB%B0%B0%EC%97%B4%EA%B3%BC-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Mon, 01 Jul 2024 06:02:12 GMT</pubDate>
            <description><![CDATA[<p>파이썬을 사용할 때 배열을 리스트라고 불러서 배열과 리스트가 같은 의미로 사용되는것으로 알고 있었다. </p>
<blockquote>
<p>하지만 다른 언어에서는 배열로 부르는것을 왜 파이썬에서는 리스트라고 불리는거야?</p>
</blockquote>
<p>분명 의미는 같을지언정 내부적으로 뭔가 다를것이라고 생각이 들었다. 
간단한 코드를 통해 검증해 보면서 배열과 리스트의 차이점에 대해 알 수 있었다. 
_(리스트는 내장된 데이터 구조로 바로 사용가능하지만 배열은 array 모듈이나 numpy 모듈을 사용해야한다고 한다.) _</p>
<h2 id="1-데이터-타입">1. 데이터 타입</h2>
<pre><code class="language-python">import array

t_list = [1, 2, &#39;test&#39;, [1,2,3], {&quot;key&quot;:1}]
t_arr = array.array(&quot;i&quot;,[1,2,3])</code></pre>
<blockquote>
<p>파이썬의 리스트에는 여러가지 데이터 타입을 담아서 사용 할 수 있다. 
하지만 배열은 단일 데이터 타입만을 허용한다. </p>
</blockquote>
<p>단일 타입만을 허용하는데 t_arr에서 <strong>&quot; i &quot;</strong> 는 뭐지??
-&gt; &quot;i&quot;는  signed integer (정수형 4바이트 데이터)를 의미하는 TypeCode코드이다.
array 모듈에서 배열을 사용할때 2개의 매개변수를 받는데 첫 위치가 TypeCode이다. 
TypeCode는 저장할 데이터 타입을 정하는건데 아래와 같이 array 모듈에 작성되어있다. </p>
<p><img src="https://velog.velcdn.com/images/hwstar-1204/post/67e486c8-e567-40bf-821a-bfb8a28529e7/image.png" alt=""> </p>
<p>공식문서를 찾아본 결과 이렇게 정의되어 있는것을 볼 수 있었다. 
<a href="https://docs.python.org/ko/3/library/array.html">https://docs.python.org/ko/3/library/array.html</a> 
<img src="https://velog.velcdn.com/images/hwstar-1204/post/3f54fa00-d792-4a79-a6aa-a2af9bb54dfb/image.png" alt=""> </p>
<h2 id="2-메모리-사용">2. 메모리 사용</h2>
<pre><code class="language-python">import array, sys

arr1 = []  # 리스트
arr2 = array.array(&quot;i&quot;,arr1)  # 배열

# 결과 1
print(f&quot;arr1 : {sys.getsizeof(arr1)} bytes&quot;)  # arr1: 56 bytes
print(f&quot;arr2 : {sys.getsizeof(arr2)} bytes&quot;)  # arr2: 80 bytes

arr1 = [i for i in range(10)]  # 정수형 데이터 10개 추가 
arr2 = array.array(&quot;i&quot;, arr1)  

# 결과 2
print(f&quot;arr1: {sys.getsizeof(arr1)} bytes&quot;)  # arr1: 184 bytes
print(f&quot;arr2: {sys.getsizeof(arr2)} bytes&quot;)  # arr2: 120 bytes</code></pre>
<p>같은 개수의 정수형 데이터를 각 변수에 리스트와 배열로 저장한 후 메모리 차지 공간을 출력해보았다. 
출력 결과는 <em>내부 정수형 데이터의 크기를 포함하지 않은 객체 자체</em> 의 크기를 출력한 것이다.</p>
<blockquote>
</blockquote>
<h4 id="결과-1">결과 1</h4>
<p>기본 리스트와 배열의 객체 크기 비교 
: 리스트 (56 bytes) &lt; 배열 (80 bytes)</p>
<h4 id="결과-2">결과 2</h4>
<p>같은 정수 데이터를 저장한 리스트와 배열의 객체 크기 비교 
: 리스트 (180 bytes) &lt; 배열 (120 bytes)</p>
<blockquote>
</blockquote>
<p>위 결과의 원인은 리스트와 배열의 <strong>메모리 관리 방식 차이</strong>에 있다. </p>
<ul>
<li>리스트는 각 요소에 대한 포인터를 저장한다.</li>
<li>배열은 동일 타입의 데이터를 연속된 블록에 저장한다. <blockquote>
</blockquote>
결과 2의 리스트와 배열 크기 계산</li>
<li>리스트 184 bytes = 리스트 객체(56) + 포인터 배열 (8 X 10) + 미리 할당된 포인터(8 X 6)</li>
<li>배열  120 bytes = 배열 객체(80) + 정수형 데이터 (4 X 10)</li>
</ul>
<p>64bit 시스템에서 각 포인터는 8 bytes를 차지한다. 
만약 전체 (내부 정수형 데이터를 포함한 객체)의 크기를 출력해보면 리스트의 크기가 훨씬 크게 출력되는데 포인터 배열에 저장된 데이터의 주소는 정수형 데이터를 포함한 정수형 객체를 가리키고 있기 때문이다.<br>자세한 사항은 <a href="https://velog.io/@l_cloud/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EA%B3%BC-%EB%A9%94%EB%AA%A8%EB%A6%AC#%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-%EA%B0%9D%EC%B2%B4%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%96%B4%EB%8A%90-%EC%98%81%EC%97%AD%EC%97%90-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%A0%80%EC%9E%A5%EB%90%98%EB%8A%94%EA%B0%80">여기를</a> 참고하는게 좋을것 같다. </p>
<h2 id="3-크기-조절">3. 크기 조절</h2>
<p>위의 결과2에서 리스트의 크기에 &#39;미리 할당된 포인터&#39;가 포함되어있었다. 
이러한 이유는 리스트가 값이 추가될때 마다 크기를 조금씩 증가 시키지 않고 여유롭게 미리 증가시켜 놓는다. 
이는 리스트의 객체가 크기를 유연하게 변경할 수 있도록 메모리를 할당하고 관리하기 위함이다. 
아래의 코드를 통해 확인해 보자 </p>
<pre><code class="language-python">import sys

arr1 = []
print(f&quot;리스트 객체 : {sys.getsizeof(arr1)} bytes&quot;)

for i in range(20):
    arr1.append(i)
    print(f&quot;{i+1} 개 원소: {sys.getsizeof(arr1)} bytes&quot;)</code></pre>
<p><strong>결과</strong>
<img src="https://velog.velcdn.com/images/hwstar-1204/post/825c5f96-273c-40cc-af4e-de3ad2df695f/image.png" width="200" height="200"></p>
<h2 id="4-성능">4. 성능</h2>
<p>리스트를 사용하면 다양한 데이터 타입을 다룰 수 있고 파이썬의 내장 함수와 매소드 지원이 풍부하기 때문에 유연성이나 사용성 측면에서 유리하다. 
하지만 각 요소에 대한 포인터를 저장하므로 메모리 효율성이나 데이터 접근 속도 측면에서 배열보다 성능이 떨어질 수 있다. </p>
<p>배열은 고정된 크기에서 동일 타입의 데이터를 연속하여 저장하므로 메모리 효율성이나 데이터 접근속도 측면에서 성능이 높다. </p>
<h2 id="리스트-list와-배열-arrayarray의-차이점">리스트 (<code>list</code>)와 배열 (<code>array.array</code>)의 차이점</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>리스트 (<code>list</code>)</th>
<th>배열 (<code>array.array</code>)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>데이터 타입</strong></td>
<td>다양한 데이터 타입 저장 가능</td>
<td>단일 데이터 타입 저장 (예: <code>i</code>는 정수형)</td>
</tr>
<tr>
<td><strong>메모리 사용</strong></td>
<td>상대적으로 더 많은 메모리 사용</td>
<td>메모리 사용이 더 효율적 (같은 타입의 데이터만 저장)</td>
</tr>
<tr>
<td><strong>크기 조절</strong></td>
<td>동적으로 크기 조절 가능</td>
<td>크기 조절은 가능하지만 동일 타입의 데이터만 가능</td>
</tr>
<tr>
<td><strong>성능</strong></td>
<td>다양한 작업을 지원하지만 배열보다 느림</td>
<td>특정 데이터 타입 작업에 최적화되어 빠름</td>
</tr>
</tbody></table>
<h2 id="결론">결론</h2>
<p>리스트와 배열은 각각의 장단점이 있으며, 용도와 상황에 따라 선택적으로 사용해야 한다
리스트는 유연성과 사용성이 뛰어나지만, 메모리 효율성과 성능 면에서는 배열이 더 유리하다.</p>
<p>따라서, 데이터의 특성과 작업의 성격에 따라 적절한 자료구조를 선택하는 것이 중요하다.
ex) 다양한 데이터 타입을 저장하고 관리하는 작업에는 리스트를 사용하고, 대규모 수치 데이터를 빠르게 처리해야 하는 경우에는 배열을 사용하는 것이 좋다.</p>
<h4 id="참고-자료">참고 자료</h4>
<p><a href="https://learnpython.com/blog/python-array-vs-list/">https://learnpython.com/blog/python-array-vs-list/</a>
<a href="https://siloam72761.tistory.com/entry/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-%EB%B0%B0%EC%97%B4%EA%B3%BC-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%9D%98-%EC%B0%A8%EC%9D%B4">[파이썬 자료구조] 파이썬에서 배열과 리스트의 차이</a>
<a href="https://velog.io/@mhcoma/Python-%EA%B0%9D%EC%B2%B4%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%ED%81%AC%EA%B8%B0-%EA%B5%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95">Python 객체의 메모리 크기 구하는 방법
</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Web Server 왜 사용해야해?]]></title>
            <link>https://velog.io/@hwstar-1204/Web-Server-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%ED%95%B4</link>
            <guid>https://velog.io/@hwstar-1204/Web-Server-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%ED%95%B4</guid>
            <pubDate>Tue, 25 Jun 2024 01:56:45 GMT</pubDate>
            <description><![CDATA[<p>web server가 하는 일에 대해 생각해 보면 대표적으로 2가지가 생각난다. </p>
<ol>
<li>정적 요청이 들어오면 웹 서버는 클라이언트에게 정적 리소스를 응답해 준다. </li>
<li>동적 요청이 들어오면 WAS로 다시 요청한다.</li>
</ol>
<h3 id="궁금증">궁금증</h3>
<p>2번에서 &quot; WAS로 동적 요청을 다시 할 것이라면 그냥 정적,동적 요청을 둘 다 처리할 수 있는 WAS 하나만 사용하면 되는 게 아닌가? &quot; 라는 생각이 든다.
( 스크립트 언어의 경우 web server + CGI 조합으로도 동적 요청 처리가 가능은 하다. php,nginX+CGI )</p>
<p><strong>Client -&gt; WAS -&gt; DB</strong>
이러한 이유에서 프로젝트를 할 때 WAS 서버만 사용해서 개발했던 것 같다.</p>
<p><strong>Client -&gt; Web Server -&gt; WAS -&gt; DB</strong>
하지만 동적 처리가 필요한 곳에서 WAS 서버만을 사용해서 서비스하고 있지 않고 위와 같이 구성하고 있다. 
직관적으로 생각해 보았을 때 정적,동적 리소스를 분리해서 관리하고 요청과 응답을 받을 수 있다는 것을 장점으로 생각해 볼 수 있다. </p>
<p>두 개 서버 조합의 장점이 이것뿐만이 아닐 것이라고 생각하여 검색해 본 결과 생각보다 여러 이유가 존재했다. </p>
<blockquote>
</blockquote>
<p><strong>web server + WAS 조합의 장점</strong></p>
<ol>
<li>정적, 동적 요청에 각 서버가 최적화 되어있으므로 처리의 효율성이 증가</li>
<li>WAS가 동적 요청만 처리하면 되므로 서버 부하 감소</li>
<li>웹 애플리케이션의 성능과 보안 향상 (reverse proxy, load balance)</li>
</ol>
<h3 id="1-정적-동적-요청에-각-서버가-최적화-되어있으므로-처리의-효율성이-증가">1. 정적, 동적 요청에 각 서버가 최적화 되어있으므로 처리의 효율성이 증가</h3>
<ul>
<li><p>Web Server 
주로 정적인 리소스(HTML, CSS, JavaScript, 이미지)를 빠르게 제공하기 위해 설계되었다. 
단순히 파일 시스템에 저장된 리소스들을 읽어서 제공하므로 처리 속도가 빠르다. 
또한 서버 입장에서 동일한 요청이 반복될 때 디스크 I/O를 줄이고 메모리에서 직접 파일을 제공하는 캐싱을 지원한다.  </p>
</li>
<li><p>WAS (Web Application Server)
주로 동적 콘텐츠를 생성하고 복잡한 비지니스 로직, DB와의 상호작용하는 일에 최적화 되어있다. 단순히 파일 읽기와 다르게 코드 실행, 다양한 연산을 수행하는 것이 가능하다. </p>
</li>
</ul>
<h3 id="2-was는-동적-요청만-처리하면-되므로-서버-부하-감소">2. WAS는 동적 요청만 처리하면 되므로 서버 부하 감소</h3>
<p>WAS는 DB 조회 및 애플리케이션 로직 처리에 집중해야 한다. 
분리하지 않는다면 WAS는 정적, 동적 요청을 모두 처리해야 하게 되는데 이는 서버에 부하가 발생하여 처리 속도가 느려질 것이다. </p>
<p>만약에 WAS에 요청이 크게 증가하면 장애가 발생할 수 있고 리소스 전달 불가 상태로 사용자에게 불편함을 주게된다. 
장애로 인한 WAS를 재시작 해야하는 경우에 앞단에 web server가 있다면 web server는 WAS으로의 요청을 차단한 후 사용자에게 서버에 문제가 있음을 html 파일만으로도 알릴 수 있게 되어 피드백을 줄 수 있게 된다. </p>
<p>혹은 동적인 요청이 많아지면 WAS를 증설하여 운영하는것도 가능하다. 이러한 방식을 &#39;scale out&#39; 이라고 부르는데 요즘 클라우드 환경에서는 이것을 사용하여 더 쉽게 트래픽에 유연하게 대응할 수 있도록 해준다. </p>
<h3 id="3-웹-애플리케이션의-성능과-보안-향상">3. 웹 애플리케이션의 성능과 보안 향상</h3>
<p>web server 앞쪽에서 reverse proxy 설정을 해놓으면 성능과 보안 측면에서 이점이 크다. </p>
<ul>
<li><p>client의 요청이 들어오면 reverse proxy가 web server의 중간에 위치하여 client 대신 적당한 서버에 요청하게 되고 해당 서버로부터 응답을 받게 된다. 
client는 어떤 서버에 요청했는지 모른다. 접속 서버의 IP 주소를 노출하지 않고 client는 요청만 하는 것으로 보안에 이점이 있다.
여기에서 적당한 서버에 요청한다고 하였는데 이는 대량의 트래픽이 특정 서버에 몰리지 않게 트래픽을 분산시키는 로드 밸런싱을 사용한 것을 말한다.
이를 통해 서버 과부하를 방지하고 응답 속도를 개선하며 가용성을 높일 수 있게 된다. </p>
</li>
<li><p>보통 client와 server간에 통신을 할때 서버 측에서 SSL을 사용하여 암호화, 복호화를 하는 경우가 많다. 
이를 reverse proxy가 대신 암호화,복호화를 해주면 서버 측의 부하도 줄이고 SSL 인증서를 중앙에서 관리할 수 있게 된다. </p>
</li>
</ul>
<hr>
<p>이러한 아키텍처의 이점을 가져가기 위해 사용되는 웹 서버에는 어떤 것들이 있을까?</p>
<h4 id="대표적인-web-server의-종류">대표적인 Web server의 종류</h4>
<ol>
<li><strong>Apache</strong> <ul>
<li>Prefork MPM(멀티 프로세스 모듈)
: 각 요청마다 프로세스 생성하지 않고 미리 만들어놓고 사용하는 방식</li>
<li>Worker MPM 
: 각 요청마다 스레드를 할당하여 작업하는 방식</li>
<li>안정성 : 오랫동안 사용해 오며 다양한 운영 환경에서의 신뢰성으로 검증된 모듈들을 제공</li>
</ul>
</li>
<li><strong>Nginx</strong><ul>
<li>비동기 event-driven 방식
: 고정된 프로세스를 생성하고 event-handler를 통해 비동기 방식으로 처리 </li>
<li>성능과 가벼움
: Apache에 비해 적은 리소스를 사용하여 높은 동시 접속 처리 가능</li>
</ul>
</li>
</ol>
<p>Apache는 프로세스,스레드 생성 비용이 들어 대용량 요청에서 한계를 보이는데 Apche 2.4 버전 이후로는 Event MPM을 지원하면서 nginx와 유사한 기능을 제공한다. 이보다 더 많은 종류가 있고 각 웹서버는 고유의 장단점을 가지고 있다. 
그러므로 특정 상황이나 요구 사항에 따라 조건을 따져보고 선택하는 것이 좋을것이다. </p>
<hr>
<h3 id="결론">결론</h3>
<p>웹 서버와 WAS를 조합하여 사용하는 아키텍처는 각 서버의 특성과 장점을 살려 효율성과 성능, 보안을 극대화할 수 있다. 웹 서버는 정적 리소스를 빠르게 제공하고, WAS는 동적 요청과 비즈니스 로직 처리에 집중하여 처리한다. 이를 통해 서버의 부하를 분산시키고, 장애 발생 시에도 빠른 대응이 가능해지게된다. 또한, 리버스 프록시와 로드 밸런싱을 통해 보안과 성능을 향상시키고, 유연한 트래픽 관리가 가능하다. Apache는 다양한 모듈과 안정성을 바탕으로 오랜 기간 신뢰를 받아왔고, Nginx는 비동기 이벤트 기반 처리 방식으로 높은 성능과 효율성을 제공한다. </p>
<p>결국, 웹 서버와 WAS의 조합은 현대 웹 애플리케이션에서 높은 성능과 안정성을 보장하기 위한 필수적인 아키텍처라고 생각했다. 
이를 통해 사용자에게 빠르고 안정적인 서비스를 제공할 수 있게되고, 향후 트래픽 증가에도 유연하게 대응할 수 있는 인프라를 구축할 수 있다.  </p>
<h3 id="더-공부해보고-싶은-점">더 공부해보고 싶은 점</h3>
<p>파이썬 백엔드에서 사용되는 WSGI,ASGI
쿠버네티스 환경에서의 서버 scale out 방식
MPM과 Event-driven 구체적으로 이해하기
캐싱과 CDN을 사용하여 웹 애플리케이션 성능 최적화하는 방법</p>
<p>[출처]
<a href="https://yozm.wishket.com/magazine/detail/1780/">https://yozm.wishket.com/magazine/detail/1780/</a>
<a href="https://hstory0208.tistory.com/entry/Nginx%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80-Apache%EC%99%80-%EC%B0%A8%EC%9D%B4%EC%A0%90">https://hstory0208.tistory.com/entry/Nginx</a>
<a href="https://codenme.tistory.com/82">https://codenme.tistory.com/82</a>
<a href="https://www.youtube.com/watch?v=Zimhvf2B7Es">https://www.youtube.com/watch?v=Zimhvf2B7Es</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django repath URL 정규 표현식 사용법]]></title>
            <link>https://velog.io/@hwstar-1204/Django-repath-URL-%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@hwstar-1204/Django-repath-URL-%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Mon, 15 Apr 2024 13:55:57 GMT</pubDate>
            <description><![CDATA[<p><strong>repath</strong>는 Django의 URL 패턴을 정의하는 함수 중 하나이다. 
이 함수는 정규 표현식을 사용하여 URL 패턴을 매칭시키고 매칭된 요청이 들어오면 특정 뷰를 호출하게 된다. 
<strong>장점</strong> : 더욱 유연하게 URL을 구성 할 수 있게 된다. </p>
<blockquote>
</blockquote>
<pre><code class="language-python">from django.urls import re_path
&gt;
re_path(r&#39;^pattern/$&#39;, view_function, name=&#39;pattern-name&#39;)
&gt;</code></pre>
<p>첫번째 인자 : 정규 표현식을 이용한 URL Pattern
두번째 인자 : 해당 URL 매칭시 호출 할 view fuction
세번째 인자 : URL 패턴 이름 부여, templete에서 url 참조시 사용</p>
<ul>
<li>메타 문자<ul>
<li>특수한 문자의 의미 </li>
<li>^ : 문자열의 시작 </li>
<li>$ : 문자열의 끝 </li>
</ul>
</li>
<li>문자 클래스 <ul>
<li>여러 문자중 하나와 일치</li>
<li>[star] : &#39;s&#39;, &#39;t&#39;, &#39;a&#39;, &#39;r&#39; 중 하나와 일치</li>
</ul>
</li>
<li>반복자<ul>
<li>이전 패턴이 나타날 수 있는 횟수</li>
<li>* : 0회 이상</li>
<li>+ : 1회 이상</li>
<li>? : 0회 or 1회 등</li>
</ul>
</li>
<li>그룹화 <ul>
<li>패턴을 그룹화 시킨다. </li>
<li>(st)+ : &#39;st&#39;, &#39;stst&#39;, &#39;ststst&#39;와 같은 문자열과 일치</li>
</ul>
</li>
<li>특수 문자 클래스<ul>
<li>특수한 표현</li>
<li>\d : 숫자와 일치, \w : 단어문자(영어,숫자,밑줄)와 일치 </li>
</ul>
</li>
<li>대안<ul>
<li>여러 패턴 중 하나와 일치 (or 연산자 느낌) </li>
<li>a|b : 문자열 a 또는 b와 일치 </li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>