Python - 접근 제어자

안녕하세요, Python 프로그래머를 꿈꾸는 여러분! 오늘은 Python에서 접근 제어자(access modifiers)의 세계로 흥미진진한 여행을 떠나보겠습니다. 프로그래밍에 새로운 여행자라도 걱정하지 마세요; 저는 이 개념을 단계별로 여러분을 안내해 드리겠습니다. 예제와 설명도 많이 들려드릴 테니, 시작해보죠!

Python - Access Modifiers

Python의 접근 제어자

객체지향 프로그래밍에서는 클래스 멤버(속성과 메서드)의 가시성과 접근성을 제어하기 위해 접근 제어자를 사용합니다. 많은 프로그래밍 언어는 공개(public), 비공개(private), 보호(protected)와 같은 엄격한 접근 제어자를 가지고 있지만, Python은 더 여유로운 접근 방식을 채택하고 있습니다. 이는 종종 "우리는 모두 동의하는 성인이라는" 철학을 따릅니다.

Python에서는 세 가지 종류의 접근 제어자가 있습니다:

  1. 공개
  2. 보호
  3. 비공개

이 각각을 예제와 함께 탐구해보겠습니다.

공개 멤버

Python에서는 모든 멤버가 기본적으로 공개입니다. 이는 클래스 외부에서 접근할 수 있다는 의미입니다. 예제를 보겠습니다:

class Student:
def __init__(self, name, age):
self.name = name  # 공개 속성
self.age = age    # 공개 속성

def display_info(self):  # 공개 메서드
print(f"Name: {self.name}, Age: {self.age}")

# Student 인스턴스 생성
student1 = Student("Alice", 20)

# 공개 멤버 접근
print(student1.name)  # 출력: Alice
student1.display_info()  # 출력: Name: Alice, Age: 20

이 예제에서 name, age, display_info()는 모두 공개 멤버입니다. 우리는 클래스 외부에서 직접 접근할 수 있습니다.

보호 멤버

보호 멤버는 멤버 이름 앞에 단일 밑줄(_)을 붙여 표시합니다. 이들은 정말로 비공개가 아니며, 클래스 외부에서도 접근할 수 있지만, 내부 사용으로 취급하는 관례입니다.

class Employee:
def __init__(self, name, salary):
self._name = name      # 보호 속성
self._salary = salary  # 보호 속성

def _display_salary(self):  # 보호 메서드
print(f"{self._name}'s salary is ${self._salary}")

# Employee 인스턴스 생성
emp1 = Employee("Bob", 50000)

# 보호 멤버 접근 (참고: 가능하지만 권장하지 않음)
print(emp1._name)  # 출력: Bob
emp1._display_salary()  # 출력: Bob's salary is $50000

_name, _salary, _display_salary()에 접근할 수는 있지만, 클래스 외부나 하위 클래스에서 접근하는 것은 일반적으로 권장하지 않습니다.

비공개 멤버

비공개 멤버는 멤버 이름 앞에 두 개의 밑줄(__)을 붙여 표시합니다. Python은 이러한 멤버에 대해 이름 맹핑(name mangling)을 수행하여, 클래스 외부에서 접근하기 어렵게 하지만, 불가능ではありません.

class BankAccount:
def __init__(self, account_number, balance):
self.__account_number = account_number  # 비공개 속성
self.__balance = balance                # 비공개 속성

def __display_balance(self):  # 비공개 메서드
print(f"Balance: ${self.__balance}")

def public_display(self):
self.__display_balance()

# BankAccount 인스턴스 생성
account1 = BankAccount("123456", 1000)

# 비공개 멤버 접근 시도
# print(account1.__account_number)  # AttributeError를 일으킬 것입니다
# account1.__display_balance()      # AttributeError를 일으킬 것입니다

# 공개 메서드를 통해 비공개 메서드 접근
account1.public_display()  # 출력: Balance: $1000

이 예제에서 __account_number, __balance, __display_balance()는 모두 비공개 멤버입니다. 클래스 외부에서 직접 접근하려고 시도하면 AttributeError가 발생합니다.

이름 맹핑

제가 비공개 멤버가 Python에서 정말로 비공개가 아님을 언급한 것 기억하시나요? 이는 이름 맹핑 메커니즘 때문입니다. 두 개의 밑줄을 사용하여 비공개 멤버를 만들면, Python은 내부적으로 그 이름을 변경하여 실수로 접근하지 않도록 합니다.

이렇게 작동하는 것을 보겠습니다:

class NameManglingDemo:
def __init__(self):
self.__private_var = "저는 비공개입니다!"

demo = NameManglingDemo()
print(dir(demo))
# 출력: [..., '_NameManglingDemo__private_var', ...]

# 맹핑된 이름을 사용하여 비공개 변수에 접근
print(demo._NameManglingDemo__private_var)  # 출력: 저는 비공개입니다!

보시다시피, Python은 __private_var_NameManglingDemo__private_var로 이름을 변경합니다. 이것이 이름 맹핑의 작동 방식입니다!

Python 프로퍼티 객체

Python의 property() 함수는 내장 함수로, 프로퍼티 객체를 생성하고 반환합니다. 이는 클래스 속성에 게터, 세터, 딜리터 메서드를 추가하는 방법입니다.

예제를 보겠습니다:

class Temperature:
def __init__(self, celsius):
self._celsius = celsius

def get_fahrenheit(self):
return (self._celsius * 9/5) + 32

def set_fahrenheit(self, fahrenheit):
self._celsius = (fahrenheit - 32) * 5/9

fahrenheit = property(get_fahrenheit, set_fahrenheit)

# 프로퍼티 사용
temp = Temperature(25)
print(temp.fahrenheit)  # 출력: 77.0

temp.fahrenheit = 86
print(temp._celsius)  # 출력: 30.0

이 예제에서 fahrenheit는 화씨 온도를 얻고 설정할 수 있는 프로퍼티로, 내부적으로 섭씨 온도로 저장합니다.

게터와 세터 메서드

게터와 세터는 클래스 속성의 값을 얻고 설정하는 메서드입니다. 이들은 무릎 속성에 접근하면서 캡슐화를 유지하는 방법을 제공합니다.

다음은 @property 데코레이터를 사용하여 게터와 세터를 구현하는 더 Pythonic한 방법의 예제입니다:

class Person:
def __init__(self, name, age):
self._name = name
self._age = age

@property
def name(self):
return self._name

@name.setter
def name(self, value):
if not isinstance(value, str):
raise ValueError("Name은 문자열이어야 합니다")
self._name = value

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

@age.setter
def age(self, value):
if not isinstance(value, int) or value < 0:
raise ValueError("Age는 양수 정수여야 합니다")
self._age = value

# 게터와 세터 사용
person = Person("Charlie", 30)
print(person.name)  # 출력: Charlie

person.name = "David"
print(person.name)  # 출력: David

try:
person.age = -5
except ValueError as e:
print(e)  # 출력: Age는 양수 정수여야 합니다

이 예제에서 nameage에 대한 게터와 세터 메서드를 만들었습니다. 세터 메서드에는 값이 특정 기준을 충족하는지 확인하는 검증이 포함됩니다.

다음은 우리가 논의한 방법을 요약한 Markdown 표입니다:

메서드 설명 예제
공개 어디서나 접근 가능 self.name
보호 클래스 및 하위 클래스 내에서 접근 가능 (관례) self._name
비공개 접근을 제한하기 위해 이름 맹핑 self.__name
프로퍼티 프로퍼티 객체를 생성 property(get_method, set_method)
게터 속성의 값을 얻는 메서드 @property
세터 속성의 값을 설정하는 메서드 @attribute.setter

이렇게 접근 제어자, 이름 맹핑, 프로퍼티 객체, 게터와 세터 메서드에 대해 다루었습니다. 기억해주세요, Python의 접근 제어는 더는 규칙보다는 관례와 신뢰에 기반을 둡니다. Python 여행을 계속하면서, 이 유연성은 깔끔하고 가독성이 좋은 코드를 작성하는 데 도움이 되며, 필요할 때 캡슐화를 구현할 수 있는 방법을 제공합니다.

연습을 계속하고, 호기심을 지켜두며, 즐거운 코딩 되세요!

Credits: Image by storyset