Python

Contents



Project Setup - venv

  • Why to use :
    • to install modules independently
    • to manage modules independently
  • How
    • setup virtual environment

        python -m venv venv
      
    • activate virtual environment

        # In Windows 
        venv\Scripts\activate
      
        # In Mac or Linux
        source venv/bin/activate
      
    • manage modules

        # install
        pip install {module_name}
      
        # check the list of modules
        pip list
      
        # get the list within txt file
        pip freeze > requirements.txt
      
        # install module list using txt file
        pip install -r requirements.txt
      
    • deactivate virtunal environment

        deactivate
      

Docstring Convention

  • function

      def function_name(param1: int, param2: str) -> bool:
          """
          한 줄 요약: 함수가 수행하는 작업을 간단히 설명합니다.
    
          여러 줄 설명: 함수의 동작, 사용법, 특별한 조건 등을 설명합니다.
          예를 들어, param1은 X를 의미하며, param2는 Y의 역할을 합니다.
    
          Args:
              param1 (int): 설명1.
              param2 (str): 설명2.
    
          Returns:
              bool: 반환값에 대한 설명.
          """
          return True
    
  • class

      class ClassName:
          """
          클래스 요약: 클래스의 역할이나 목적을 간단히 설명합니다.
    
          클래스의 동작, 속성, 주요 메서드 등을 상세히 기술합니다.
          예를 들어, 이 클래스는 X 작업을 수행하며 Y 속성을 관리합니다.
    
          Attributes:
              attr1 (type): 속성1의 설명.
              attr2 (type): 속성2의 설명.
          """
    
          def __init__(self, param1: int, param2: str) -> None:
              """
              클래스의 생성자: 인스턴스를 초기화합니다.
    
              Args:
                  param1 (int): 설명1.
                  param2 (str): 설명2.
              """
              self.param1 = param1
              self.param2 = param2
    
    

Absolute Import vs Relative Import

  • Two terms are for where does code start to track
  • Absolute Import
    • start from root directory of project
  • Relative Import
    • start from current script file
  • In python2, there was a confliction issue between original module name and created module name. So for the sake of avoiding the confliction, need to use from __future__ import absolute_import
  • In python3, this condition or setup become default, so this is not necessary

Module

__future__

Deferred evaluation by storing module as string

  • This is for preventing the codes from circular import

      from typing import TYPE_CHECKING
    
      if TYPE_CHECKING:
          from mod1 import School
    
      class Student:
          def register_school(school: 'School'):
              pass
    

typing

Union

  • This represents that parameter type and return type can be one of several specific types
  • | could be used instead of Union from 3.10 onwards

      from typing import Union
    
      def parse_data(data: Union[str, list]) -> list:
          if isinstance(data, str):
              return data.split(",")  # 문자열을 리스트로 변환
          return data
    
      def get_data() -> Union[str, dict]:
          # 조건에 따라 반환 타입이 달라질 수 있음
          if some_condition:
              return "data as string"
          return {"key": "value"}
    
    

pandas

  • CRUD
    • C : create
      • create empty dataframe
      • create dataframe from list of dict
    • R : read
      • search row by condition ( column name cell value )
      • selecting and indexing from Dataframe to sub dataframe ( filtering )
      • read and access exact cell
    • U : update
      • add row with dictionary
    • D : delete
  • Utility
    • DataFrame.copy()
    • DataFrame.to_dict(“index”)
    • DataFrame.to_dict(orient=”records”)

Create

  • Create table
      def create_table(default_info :dict) -> pd.DataFrame:
          return pd.DataFrame(default_info)
    
      default_header = {"task_name":[], "task_id":[], "assignee":[], "entity_id":[]}
      table = create_table(default_header)
      print(table)
    
      # =============
      # Print results
      # =============
      # Empty DataFrame
      # Columns: [task_name, task_id, assignee, entity_id]
      # Index: []
    

Update

  • Add rows
      def add_row_to_table(tar_table :pd.DataFrame, input_info :dict) -> None:
          tar_table.loc[len(tar_table)] = input_info
    
      assignee = [{"name":"송태영"}]
      assignee_str = dumps(assignee)
    
      input_infos = [
                      {"task_name":"fx01", "task_id":123, "assignee":assignee_str, "entity_id":456},
                      {"task_name":"fx02", "task_id":111, "assignee":assignee_str, "entity_id":333},
                      {"task_name":"fx03", "task_id":122, "assignee":assignee_str, "entity_id":555},
                  ]
      for _info in input_infos:
          add_row_to_table(table, _info)
    
      print(table)
    
      # =============
      # Print results
      # =============
      #     task_name  task_id                          assignee  entity_id
      # 0      fx01      123  [{"name": "\uc1a1\ud0dc\uc601"}]        456
      # 1      fx02      111  [{"name": "\uc1a1\ud0dc\uc601"}]        333
      # 2      fx03      122  [{"name": "\uc1a1\ud0dc\uc601"}]        555
    

Read

  • Search row by condition ( filtering )

      def search_in_table(tar_table :pd.DataFrame, col_name :str, input_info :str) -> pd.DataFrame:
          return tar_table.loc[tar_table[col_name] == input_info]
    
      res = search_in_table(table, "task_id", 123)
      print(res)
    
  • Select and Index from Dataframe to sub dataframe ( filtering )

      # 한개의 column
      print(table["task_name"] )
    
      # 선택한 column 들
      print(table[["task_name", "entity_id"]] )
    
    
      # 한개의 row
      print(table.loc[[1]] )
    
      # 선택한 row들
      print(table.loc[[0,2]])
    
  • Access exact cell

      print(table.iloc[0]["task_name"])
      print(loads(table.iloc[0]["assignee"]))
      print(table.to_dict("index"))
      print(table.to_dict(orient='records'))
    
      # =============
      # Print results
      # =============
      # fx01
      # [{'name': '송태영'}]
      # {0: {'task_name': 'fx01', 'task_id': 123, 'assignee': '[{"name": "\\uc1a1\\ud0dc\\uc601"}]', 'entity_id': 456}}
      # [{'task_name': 'fx01', 'task_id': 123, 'assignee': '[{"name": "\\uc1a1\\ud0dc\\uc601"}]', 'entity_id': 456}]
    

logging

  • This example below is about how to pring logs into both console and log file
  • 루닥스 . (2024) . Python logging 의 이해
    • https://rudaks.tistory.com/entry/Python-logging%EC%9D%98-%EC%9D%B4%ED%95%B4
import logging

# Create logger
logger = logging.getLogger('my_logger')
logger.setLevel(logging.DEBUG)

# set file handler up
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.INFO)

# set console handler up
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)

# set format up
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# add both of two handler
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# use logs
logger.debug("This is a DEBUG message")
logger.info("This is an INFO message")
logger.warning("This is a WARNING message")
logger.error("This is an ERROR message")
logger.critical("This is a CRITICAL message")

Context Manager (with Statement)

Python의 with 구문은 자원 획득과 해제를 보장하는 Context Manager 패턴의 문법적 표현입니다. try-finally로 동일한 동작을 구현할 수 있지만, with는 해제 코드(__exit__)를 문법 구조로 강제하기 때문에 프로덕션 파이프라인에서 인적 실수를 원천 차단합니다.

with vs try-finally

with 블록을 나가는 순간 Python은 대상 객체의 __exit__() 메서드를 자동 실행합니다. as 뒤의 변수는 해당 객체의 __enter__() 반환값이며, 함수 자체의 return 값이 아닙니다.

# try-finally: 자원 해제 코드를 개발자가 직접 작성 — 누락 위험
loader = bpy.data.libraries.load(blend_path, link=False)
data_from, data_to = loader.__enter__()
try:
    if "Hero_Rig" in data_from.objects:
        data_to.objects.append("Hero_Rig")
finally:
    loader.__exit__(None, None, None)

# with: __exit__ 자동 실행 — 누락 불가
with bpy.data.libraries.load(blend_path, link=False) as (data_from, data_to):
    if "Hero_Rig" in data_from.objects:
        data_to.objects.append("Hero_Rig")

Pipeline Applications (Blender)

Context Manager는 파이프라인 TD가 DCC 내 데이터 무결성을 지킬 때 세 가지 패턴으로 반복 사용됩니다.

File I/O — ShotGrid / JSON metadata

import json

with open("/path/to/shot_meta.json", "r", encoding="utf-8") as file:
    try:
        shot_data = json.load(file)
    except json.JSONDecodeError:
        print("JSON 파일 파싱 실패")

외부 .blend 라이브러리에서 특정 데이터만 선별해 현재 씬으로 가져오는 패턴입니다. with 블록 안에서는 가져올 대상 이름을 data_to에 담기만 하고, 실제 데이터 참조 및 후속 작업(Modifier 할당 등)은 블록 밖에서 수행해야 안전합니다. 네트워크 스토리지 환경에서는 try-except를 병행해 I/O 크래시를 방지합니다.

import bpy

blend_file_path = "/net/storage/assets/hero_prop.blend"

with bpy.data.libraries.load(blend_file_path, link=False) as (data_from, data_to):
    # data_from: 외부 파일의 데이터 목록 (읽기 전용)
    # data_to:   현재 씬으로 가져올 대상 목록
    if "Rig_IronMan" in data_from.objects:
        data_to.objects.append("Rig_IronMan")

# 블록 밖에서 후속 처리 — with 블록 종료 후 데이터가 bpy.data에 병합됨

Artist Context Preservation — custom context manager

파이프라인 툴이 오브젝트 선택 상태나 편집 모드를 임시 변경하다 크래시하면 아티스트의 작업 상태가 파손됩니다. contextlib.contextmanager로 커스텀 컨텍스트 매니저를 작성하면 어떤 예외가 발생하더라도 원래 상태로 복원됩니다. 이 패턴은 대규모 스튜디오 자동화 툴셋의 프레임워크 베이스에 심어두는 것이 권장됩니다.

import bpy
from contextlib import contextmanager

@contextmanager
def preserve_user_context(context):
    """아티스트의 활성 오브젝트, 모드, 선택 상태를 보존하는 컨텍스트 매니저."""
    active_obj = context.active_object
    original_mode = context.mode if active_obj else 'OBJECT'
    selected_objs = context.selected_objects[:]  # shallow copy

    try:
        yield
    finally:
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
        bpy.ops.object.select_all(action='DESELECT')

        for obj in selected_objs:
            try:
                obj.select_set(True)
            except ReferenceError:
                pass  # 스크립트 실행 중 삭제된 오브젝트 예외 처리

        if active_obj and active_obj.name in context.view_layer.objects:
            context.view_layer.objects.active = active_obj
            if original_mode in ['EDIT', 'SCULPT', 'PAINT_TEXTURE']:
                bpy.ops.object.mode_set(mode=original_mode)


# Usage
with preserve_user_context(bpy.context):
    bpy.ops.mesh.primitive_cube_add()
    bpy.ops.object.mode_set(mode='EDIT')
    # 에러가 발생해도 아티스트 작업 상태는 with 종료 시 자동 복원됨

Related: Blender pipeline 응용 맥락은 Blender 포스트의 bpy.types.Operator 섹션에서 이어집니다.

results matching ""

    No results matching ""