Menu
Coddy logo textTech

프라이빗 속성

Coddy Python 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 64개 중 15번째.

프라이빗 속성(Private attributes)은 특정 데이터가 클래스 외부에서 직접 액세스되어서는 안 된다는 것을 나타내기 위해 밑줄(underscore)을 사용합니다.

왜 프라이빗 속성이 필요할까요? 은행 계좌가 있다고 가정해 봅시다. 누군가가 여러분의 잔액을 직접 변경하는 것을 원하지 않을 것입니다. 은행이 거래를 검증하고, 사기를 확인하며, 기록을 유지할 수 있도록 적절한 채널(입금/출금 메서드)을 거쳐야 합니다. 프라이빗 속성도 같은 방식으로 작동합니다. 데이터가 잘못 변경되지 않도록 보호합니다.

다음은 단일 언더스코어("내부 사용"을 위한 관례)를 사용하는 예시입니다:

class Person:
    def __init__(self, name, age):
        self._name = name    # "protected" - 내부 사용용
        self._age = age      # "protected" - 내부 사용용

단일 언더스코어(_) 속성은 여전히 접근 가능하지만, 접근하지 말라는 신호입니다:

person = Person("Alice", 30)
print(person._name)  # 작동은 하지만 권장되지 않습니다

더 강력한 프라이버시(네임 맹글링)를 위해 이중 밑줄을 사용하세요:

class Person:
    def __init__(self, name, age):
        self.__name = name   # "private" - 네임 맹글링이 적용됨
        self.__age = age     # "private" - 네임 맹글링이 적용됨
    
    def get_name(self):
        return self.__name
    
    def set_age(self, age):
        if age >= 0:
            self.__age = age
        else:
            print("Age must be positive!")

왜 getter와 setter 메서드를 사용하나요? 이들은 문지기와 같습니다. 누구나 __age를 직접 변경하게 하는 대신(이는 음수 나이나 기타 유효하지 않은 데이터를 초래할 수 있습니다), 먼저 입력을 검증하는 set_age()를 사용하도록 강제합니다. 이는 버그를 방지하고 데이터 무결성을 보장합니다.

"추가 코드"에는 목적이 있습니다:

  • get_name() - 이름을 읽기 위한 제어된 액세스
  • set_age(age) - 변경을 허용하기 전에 나이를 검증합니다 (음수 나이 불가!)
  • 이러한 메서드가 없으면 누군가가 age = -100으로 설정하여 프로그램을 망가뜨릴 수 있습니다

접근자 메서드(accessor methods)를 사용하여 비공개 속성과 상호작용하세요:

person = Person("Bob", 25)
print(person.get_name())  # Bob

person.set_age(30)        # 유효함: 나이가 30이 됩니다
person.set_age(-5)        # 유효하지 않음: 나이는 양수여야 합니다! (나이는 30으로 유지됨)

이점이 보이시나요? set_age() 메서드는 유효하지 않은 데이터를 방지합니다. 이 메서드가 없다면 실수로 나이가 -5인 사람을 생성할 수도 있는데, 이는 말이 되지 않습니다!

이중 밑줄(Double underscore) 속성은 "네임 맹글링(name mangled)" 처리되지만 여전히 접근할 수 있습니다:

person = Person("Charlie", 35)
# 이 방법은 작동하지 않습니다:
# print(person.__name)  # AttributeError

# 하지만 이 방법은 작동합니다 (권장되지 않음):
print(person._Person__name)  # Charlie

실제 사례 - 왜 이 모든 코드가 필요한가요?

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # 프라이빗(Private): 직접 변경할 수 없음
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            return True
        return False
    
    def get_balance(self):
        return self.__balance

# 프라이빗 속성이 없는 경우:
# account.__balance = -1000000  # 재앙! 음수 잔액이 허용됨

# 프라이빗 속성이 있는 경우:
account = BankAccount(100)
account.deposit(50)           # 안전함: 검증됨
print(account.get_balance())  # 150

"추가 코드"(메서드)는 유효하지 않은 변경으로부터 데이터를 보호합니다. 이는 귀중품을 보호되지 않은 상태로 두는 대신 보안 요원을 두는 것과 같습니다.

출력:

Alice
Bob
Age must be positive!
Charlie
150

단일 밑줄 _attribute는 관례적으로 "내부 전용"을 의미합니다. 이중 밑줄 __attribute는 더 강력한 프라이버시를 위해 네임 맹글링(name mangling)을 유발합니다. 프라이빗 데이터와 적절하게 상호작용하고 검증을 추가하려면 게터/세터 메서드(접근자 메서드)를 사용하세요. 이러한 "추가 코드"는 데이터가 항상 유효하도록 보장함으로써 버그를 방지합니다.

challenge icon

챌린지

쉬움

user.py에 있는 User 클래스를 비공개 비밀번호 기능과 함께 완성한 후, driver.py에서 사용하세요. TODO 주석을 따라 비밀번호 확인 및 변경 메서드를 구현하세요.

치트 시트

프라이빗(Private) 속성은 밑줄(underscore)을 사용하여 클래스 외부에서 데이터에 직접 접근해서는 안 된다는 것을 나타냅니다.

단일 밑줄 (_attribute) - "내부용"을 위한 관례:

class Person:
    def __init__(self, name, age):
        self._name = name    # "protected" - 내부용
        self._age = age      # "protected" - 내부용

이중 밑줄 (__attribute) - 더 강력한 프라이버시를 위해 네임 맹글링(name mangling)을 트리거합니다:

class Person:
    def __init__(self, name, age):
        self.__name = name   # "private" - 네임 맹글링됨
        self.__age = age     # "private" - 네임 맹글링됨
    
    def get_name(self):
        return self.__name
    
    def set_age(self, age):
        if age >= 0:
            self.__age = age
        else:
            print("Age must be positive!")

프라이빗 속성과 상호작용하려면 접근자(accessor) 메서드를 사용하세요:

person = Person("Bob", 25)
print(person.get_name())  # Bob
person.set_age(30)
person.set_age(-5)        # Age must be positive!

이중 밑줄 속성은 네임 맹글링되지만 여전히 접근할 수 있습니다 (권장되지 않음):

# 이 방식은 작동하지 않습니다:
# print(person.__name)  # AttributeError

# 하지만 이 방식은 작동합니다 (권장되지 않음):
print(person._Person__name)  # Charlie

직접 해보기

# TODO: user 모듈에서 User 클래스를 가져오세요

# TODO: 입력으로부터 테스트 케이스를 가져오세요
test_case = input()

if test_case == "constructor_test":
    # TODO: 초기 비밀번호가 "secure123"인 User 객체를 생성하세요
    user = User("secure123")
    print("User created successfully")
    print("Initial password set")

elif test_case == "correct_password_test":
    # TODO: 초기 비밀번호가 "secure123"인 User 객체를 생성하세요
    user = User("secure123")
    # TODO: 올바른 비밀번호로 check_password 메서드를 테스트하세요
    #       True를 반환하면 "Password check successful!"을 출력하세요
    #       False를 반환하면 "Password check failed!"를 출력하세요
    result = user.check_password("secure123")
    if result:
        print("Password check successful!")
    else:
        print("Password check failed!")

elif test_case == "incorrect_password_test":
    # TODO: 초기 비밀번호가 "secure123"인 User 객체를 생성하세요
    user = User("secure123")
    # TODO: 잘못된 비밀번호 "wrong"으로 check_password 메서드를 테스트하세요
    #       False를 반환하면 "Incorrect password rejected!"를 출력하세요
    #       True를 반환하면 "Security issue: incorrect password accepted!"를 출력하세요
    result = user.check_password("wrong")
    if not result:
        print("Incorrect password rejected!")
    else:
        print("Security issue: incorrect password accepted!")

elif test_case == "change_password_test":
    # TODO: 초기 비밀번호가 "secure123"인 User 객체를 생성하세요
    user = User("secure123")
    # TODO: 비밀번호를 "secure123"에서 "newpass456"으로 변경하는 테스트를 수행하세요
    #       True를 반환하면 "Password changed successfully!"를 출력하세요
    #       False를 반환하면 "Password change failed!"를 출력하세요
    result = user.change_password("secure123", "newpass456")
    if result:
        print("Password changed successfully!")
    else:
        print("Password change failed!")
    
    # TODO: "newpass456"을 확인하여 새 비밀번호가 작동하는지 검증하세요
    #       True를 반환하면 "New password works!"를 출력하세요
    #       False를 반환하면 "New password doesn't work!"를 출력하세요
    if user.check_password("newpass456"):
        print("New password works!")
    else:
        print("New password doesn't work!")

elif test_case == "change_password_wrong_old_test":
    # TODO: 초기 비밀번호가 "secure123"인 User 객체를 생성하세요
    user = User("secure123")
    # TODO: 잘못된 기존 비밀번호로 비밀번호 변경을 시도하세요
    #       False를 반환하면 "Security working: incorrect old password rejected!"를 출력하세요
    #       True를 반환하면 "Security issue: password changed with wrong old password!"를 출력하세요
    result = user.change_password("wrong", "hackerpw")
    if not result:
        print("Security working: incorrect old password rejected!")
    else:
        print("Security issue: password changed with wrong old password!")

elif test_case == "comprehensive_test":
    # TODO: 초기 비밀번호가 "secure123"인 User 객체를 생성하세요
    user = User("secure123")
    print("User created with initial password")
    
    # TODO: 올바른 비밀번호로 check_password 메서드를 테스트하세요
    if user.check_password("secure123"):
        print("Initial password verification successful")
    
    # TODO: 잘못된 비밀번호로 check_password 메서드를 테스트하세요
    if not user.check_password("wrongpass"):
        print("Incorrect password properly rejected")
    
    # TODO: 비밀번호를 "secure123"에서 "newpass456"으로 변경하는 테스트를 수행하세요
    if user.change_password("secure123", "newpass456"):
        print("Password successfully changed")
    
    # TODO: 기존 비밀번호가 더 이상 작동하지 않는지 확인하세요
    if not user.check_password("secure123"):
        print("Old password no longer works")
    
    # TODO: 새 비밀번호가 작동하는지 확인하세요
    if user.check_password("newpass456"):
        print("New password works correctly")
    
    # TODO: 잘못된 기존 비밀번호로 비밀번호 변경을 시도하세요
    if not user.change_password("wrongold", "hackerpw"):
        print("Security maintained: wrong old password rejected")
    
    # TODO: 변경 시도 실패 후에도 비밀번호가 여전히 안전한지 확인하세요
    if user.check_password("newpass456"):
        print("Password remains secure after failed change attempt")

else:
    print("Unknown test case")
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨