프라이빗 속성
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)을 유발합니다. 프라이빗 데이터와 적절하게 상호작용하고 검증을 추가하려면 게터/세터 메서드(접근자 메서드)를 사용하세요. 이러한 "추가 코드"는 데이터가 항상 유효하도록 보장함으로써 버그를 방지합니다.
챌린지
쉬움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")이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.