Loading the catalog…
Loading the catalog…
Python에서는 숫자, 문자열, 리스트뿐 아니라 우리가 직접 정의한 데이터도 객체로 만들 수 있다. 프로그램이 커질수록 서로 관련된 데이터와 기능을 하나의 단위로 묶고 싶은 상황 이 생긴다. 이때 사용하는 대표적인 방법이 객체지향 프로그래밍이다. 이 글에서는 Python의 클래스와 객체를 처음 접하는 관점에서 다음 내용을 정리한다. 클래스와 인스턴스 self __init__ 인스턴스 변수와 클래스 변수 메서드 상속 특수 메서드 클래스는 왜 필요할까? 사용자 데이터를 딕셔너리로 표현할 수 있다. user = { "name": "kim", "point": 1000, } 포인트를 증가시키는 함수도 만들 수 있다. def add_point(user, amount): user["point"] += amount 작은 프로그램에서는 충분하다. 하지만 사용자와 관련된 데이터와 기능이 계속 늘어나면 이런 요구가 생긴다. 사용자의 이름과 포인트를 함께 관리하고 싶다. 포인트를 증가시키는 로직도 사용자와 묶고 싶다. 사용자 상태를 검증하는 기능도 추가하고 싶다. 같은 구조의 사용자를 여러 명 만들고 싶다. 이럴 때 클래스 를 사용할 수 있다. 클래스와 인스턴스 클래스는 객체를 만들기 위한 사용자 정의 타입이라고 이해할 수 있다. class User: pass 이 클래스로 실제 객체를 만든다. user = User() user 는 User 클래스의 인스턴스 다. print(type(user)) 메서드 클래스 내부에 정의된 함수를 메서드라고 부른다. class User: def say_hello(self): print("안녕하세요.") 호출: user = User() user.say_hello() self 는 무엇일까? 인스턴스 메서드의 첫 번째 매개변수에는 관례적으로 self 를 사용한다. class User: def say_hello(self): print("안녕하세요.") self 는 현재 메서드를 호출한 인스턴스 자신 을 가리킨다. 다음 호출을 보자. user = User() user.say_hello() 개념적으로는 다음과 연결해서 이해할 수 있다. User.say_hello(user) Python이 인스턴스를 첫 번째 인자로 전달해주기 때문에 호출할 때 직접 self 를 넘기지 않는다. __init__ 으로 객체 초기화하기 객체를 만들 때 필요한 초기값을 넣고 싶다면 __init__() 을 사용한다. class User: def __init__(self, name, point): self.name = name self.point = point 객체 생성: user = User( "kim", 1000, ) print(user.name) print(user.point) self.name , self.point 는 각 인스턴스가 보유하는 데이터다. 데이터와 메서드를 함께 묶기 class User: def __init__( self, name, point=0, ): self.name = name self.point = point def add_point( self, amount, ): self.point += amount def get_summary(self): return ( f"{self.name}: " f"{self.point}P" ) 사용: user = User("kim") user.add_point(500) user.add_point(300) print(user.get_summary()) kim: 800P 사용자의 상태와 상태를 변경하는 기능이 하나의 클래스 안에 모였다. 인스턴스 변수 각 객체마다 독립적으로 가지는 값을 인스턴스 변수라고 한다. class User: def __init__(self, name): self.name = name user1 = User("kim") user2 = User("lee") print(user1.name) print(user2.name) 각 인스턴스의 name 은 서로 독립적이다. 클래스 변수 모든 인스턴스가 공유하는 값은 클래스 변수로 만들 수 있다. class User: service_name = "My Service" def __init__(self, name): self.name = name user1 = User("kim") user2 = User("lee") print(user1.service_name) print(user2.service_name) 둘 다 같은 클래스 변수에 접근한다. 변경 가능한 클래스 변수 주의하기 다음 코드는 주의가 필요하다. class User: tags = [] 모든 인스턴스가 같은 리스트를 공유한다. user1 = User() user2 = User() user1.tags.append( "python" ) print(user2.tags) user2 에서도 "python" 이 보인다. 각 객체마다 독립적인 리스트가 필요하다면 __init__() 에서 만든다. class User: def __init__(self): self.tags = [] 클래스 메서드 @classmethod 는 첫 번째 인자로 클래스 자체를 받는다. class User: default_point = 100 def __init__( self, name, point, ): self.name = name self.point = point @classmethod def create_default( cls, name, ): return cls( name, cls.default_point, ) user = User.create_default( "kim" ) print(user.point) 대체 생성자 같은 패턴에 활용할 수 있다. 정적 메서드 @staticmethod 는 인스턴스나 클래스 상태를 자동으로 받지 않는다. class User: @staticmethod def is_valid_name(name): return len(name) >= 2 print( User.is_valid_name( "kim" ) ) 단순 유틸리티 함수라면 반드시 클래스 안에 넣어야 하는 것은 아니다. 내부 구현을 나타내는 _ Python에서는 Java의 private 처럼 강한 접근 제한자를 중심으로 설계하지 않는다. 관례적으로 외부에서 직접 사용하지 않기를 바라는 속성이나 메서드 앞에 _ 를 붙인다. class Wallet: def __init__(self): self._balance = 0 이것은 강제적인 보안 장벽이 아니라 내부 구현이라는 의도를 전달하는 관례 에 가깝다. 상속 기존 클래스의 기능을 물려받아 새로운 클래스를 만들 수 있다. class Account: def __init__( self, email, ): self.email = email def login(self): print( f"{self.email} 로그인" ) 관리자 계정: class AdminAccount( Account ): def delete_user( self, user_id, ): print( f"{user_id}번 " "사용자 삭제" ) 사용: admin = AdminAccount( "admin@example.com" ) admin.login() admin.delete_user(10) AdminAccount 에 login() 을 직접 작성하지 않았지만 부모 클래스에서 상속받았다. 메서드 오버라이딩 자식 클래스에서 부모의 메서드를 다시 정의할 수 있다. class Notification: def send( self, message, ): print( f"알림: {message}" ) class EmailNotification( Notification ): def send( self, message, ): print( f"이메일 발송: " f"{message}" ) notification = ( EmailNotification() ) notification.send( "회원가입 완료" ) 자식 클래스의 send() 가 실행된다. super() 부모 클래스의 구현을 활용하면서 기능을 확장하고 싶을 때 사용한다. class Account: def __init__( self, email, ): self.email = email class AdminAccount( Account ): def __init__( self, email, level, ): super().__init__( email ) self.level = level super() 를 이용하면 부모 클래스 이름을 직접 하드코딩하는 것보다 상속 구조 변화에 대응하기 쉽다. 다형성과 Duck Typing 같은 메서드 이름을 가진 객체들을 동일한 방식으로 다룰 수 있다. class EmailSender: def send( self, message, ): print( f"Email: {message}" ) class SmsSender: def send( self, message, ): print( f"SMS: {message}" ) senders = [ EmailSender(), SmsSender(), ] for sender in senders: sender.send( "결제가 완료되었습니다." ) 호출 코드는 객체의 정확한 클래스보다 send() 라는 필요한 동작을 제공하는지에 집중한다. 이런 특성은 Python의 duck typing 과 연결된다. 특수 메서드 Python 객체는 __이름__ 형태의 특수 메서드를 구현해서 내장 문법과 연결될 수 있다. __str__ 객체를 사람이 읽기 좋은 문자열로 표현한다. class User: def __init__( self, name, ): self.name = name def __str__(self): return ( f"User(" f"name={self.name}" f")" ) user = User("kim") print(user) __len__ len() 과 연결할 수 있다. class Team: def __init__( self, members, ): self.members = members def __len__(self): return len( self.members ) team = Team( ["kim", "lee", "park"] ) print(len(team)) __getitem__ 인덱싱 문법을 지원할 수 있다. class Team: def __init__( self, members, ): self.members = members def __getitem__( self, index, ): return self.members[ index ] team = Team( ["kim", "lee"] ) print(team[0]) 비교 연산자 < 동작을 정의하려면 __lt__() 를 구현할 수 있다. class Product: def __init__( self, price, ): self.price = price def __lt__( self, other, ): return ( self.price < other.price ) a = Product(10000) b = Product(20000) print(a < b) 객체지향을 무조건 써야 할까? 그렇지 않다. 간단한 데이터 변환은 함수만으로 충분할 수 있다. def normalize_email( email, ): return ( email .strip() .lower() ) 이 기능 하나를 위해 굳이 클래스를 만들 필요는 없다. 클래스는 다음 상황에서 특히 도움이 된다. 함께 움직이는 상태와 기능이 있다. 동일한 구조의 객체를 여러 개 생성해야 한다. 객체별 상태를 유지해야 한다. 구현을 상속하거나 교체해야 한다. 도메인 개념을 코드로 명확하게 표현하고 싶다. 실전 예제: 주문 객체 class Order: tax_rate = 0.1 def __init__( self, order_id, prices, ): self.order_id = ( order_id ) self.prices = prices def subtotal(self): return sum( self.prices ) def tax(self): return ( self.subtotal() * self.tax_rate ) def total(self): return ( self.subtotal() + self.tax() ) def __str__(self): return ( f"Order(" f"id={self.order_id}, " f"total=" f"{self.total():,.0f}원" f")" ) order = Order( order_id=101, prices=[ 12000, 25000, 8000, ], ) print(order.subtotal()) print(order.tax()) print(order.total()) print(order) 객체 내부에 주문 데이터와 계산 책임을 함께 둘 수 있다. 객체지향 설계에서 기억할 점 클래스가 커진다고 무조건 좋은 것은 아니다. 다음 신호가 보인다면 책임을 다시 나눌 필요가 있다. 메서드가 지나치게 많다. 한 클래스가 DB, API, 이메일, 계산을 모두 담당한다. 클래스 이름을 명확하게 설명하기 어렵다. 특정 메서드가 클래스의 대부분 상태를 사용하지 않는다. 객체지향의 목적은 클래스를 많이 만드는 것이 아니라 관련된 책임을 이해하기 좋은 단위로 묶는 것 이다. 핵심 정리 클래스는 데이터와 기능을 함께 표현할 수 있는 사용자 정의 타입이다. 클래스로 만든 실제 객체를 인스턴스라고 한다. self 는 현재 메서드를 호출한 인스턴스를 가리킨다. __init__() 은 인스턴스를 만들 때 초기 상태를 설정한다. 인스턴스 변수는 객체마다 독립적이고 클래스 변수는 인스턴스들이 공유한다. 상속을 이용하면 공통 기능을 재사용하고 자식 클래스에서 동작을 확장하거나 재정의할 수 있다. __str__ , __len__ , __getitem__ 같은 특수 메서드를 구현하면 Python 내장 문법과 연결할 수 있다. 간단한 문제라면 함수만으로 충분하며, 객체지향은 상태와 기능을 함께 모델링할 필요가 있을 때 사용하면 된다.
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
Python 객체지향 프로그래밍: 클래스, 인스턴스, 상속을 코드로 이해하기. Python에서는 숫자, 문자열, 리스트뿐 아니라 우리가 직접 정의한 데이터도 객체로 만들 수 있다. 프로그램이 커질수록 서로 관련된 데이터와 기능을 하나의 단위로 묶고 싶은 상황 이 생긴다. 이때 사용하는 대표적인 방법이 객체지향 프로그래밍이다. 이 글에서는 Python의 클래스와 객체를 처음 접하는 관점에서 다음 내용을 정리한다. 클래스와 인스턴스 self __init__ 인스턴스 변수와 클래스 변수 메서드 상속 특수 메서드 클래스는 왜 필요할까? 사용자 데이터를 딕셔너리로 표현할 수 있다. user = { "name": "kim", "point": 1000, } 포인트를 증가시키는 함수도 만들 수 있다. def…
Open source