본문 바로가기

PYTHON-BACK

#파이썬 기초 9일차_1

728x90

5. 캡슐화(Encapsulation)

클래스를 만들때 가장 큰 특징은 캡슐화와 은닉화이다.

 

5.1 내부 속성이나 메서드 명명 규칙 관행

5.2 보호된 이름: _이름

5.2.1 메서드로 보호 속성 감추기

  • 보호된 이름 사용
  • 언더바의 사용방식
  • getter, setter를 파이썬에서는 지원하지 않아서 이를 따로 함수로 만들어서 사용해야한다.
class Protected :
    def __init__(self,name,age) :
        self._set(name,age)

    def _set(self,name,age) :
        self._name = name
        self._age = age

    def getname(self) :
        return self._name
    def getage(self) :
        return self._age
    def setname(self, name):
      self._name = name
    def setage(self, age):
      self._age = age
p = Protected("춘식이", 3)
print(p.__dict__)

{'_name': '춘식이', '_age': 3}

print(p.getname())
print(p.getage())

춘식이

3

print(p._name)
print(p._age)

춘식이

3

5.3 맹글링(Mangling)을 이용한 정보 은닉

  • 속성이나 메소드에 대한 맹글링 처리
  • 실제 실행할때 다른 형태로 이름을 바꾸어버린다(알고 있는 이름으로 검색해도 이름이 바뀌어있어서 접근이 불가능하게 만듬, 어떤식으로 이름이 바뀌는지를 알고있으면 쉽게 접근가능해지는 한계점이 있음)
class Mangling :
    def __init__(self,name,age) :
        self.__set(name,age)

    def __set(self,name,age) :
        self.__name = name
        self.__age = age

    def getname(self) :
        return self.__name
    def getage(self) :
        return self.__age
p = Mangling("춘장이", 31)

print(p.__dict__)

{'_Mangling__name': '춘장이', '_Mangling__age': 31}

print(p.getname())
print(p.getage())

춘장이

31

print(p._Mangling__name)
print(p._Mangling__age)

춘장이

31

p.__set("맹글링",55) # 이런형태들은 접근 불가능
print(p.getname())은 접근이 가능하다(_ 안붙어 있는 것들)

 

클래스의 네임스페이스를 확인

import pprint

pprint.pprint(Mangling.__dict__)

mappingproxy({'_Mangling__set': <function Mangling.__set at 0x7ee7f7001b40>,
              '__dict__': <attribute '__dict__' of 'Mangling' objects>,
              '__doc__': None,
              '__init__': <function Mangling.__init__ at 0x7ee7f7001ab0>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Mangling' objects>,
              'getage': <function Mangling.getage at 0x7ee7f7001cf0>,
              'getname': <function Mangling.getname at 0x7ee7f7001c60>})

 

p._Mangling__set("맹글링",7)

print(p._Mangling__name)
print(p._Mangling__age)

맹글링

7

 

파이썬의 클래스는 반쪽이라 할 수 있다,

클래스의 접근 제어에 대한 메커니즘이 덜 엄격하다는 점에서 그렇다.

 

  • Public Members: 기본적으로 모든 클래스 멤버(변수 및 메서드)는 공개(public)되어 있습니다.
  • Protected Members: 밑줄 한 개 (_member)로 시작하는 멤버는 프로텍티드(protected)로 간주되지만, 이는 단지 암묵적인 규칙일 뿐 실제로 접근을 막지는 않습니다.
  • Private Members: 밑줄 두 개 (__member)로 시작하는 멤버는 네임 맹글링(name mangling)을 통해 외부에서 접근하기 어렵게 만듭니다. 하지만 완전히 접근이 불가능한 것은 아니며, 특정 네이밍 규칙을 사용하여 여전히 접근할 수 있습니다.

5.4 Property를 이용한 정보 은닉

  • 프로퍼티로 속성을 숨기기
  • 파이썬의 property 데코레이터를 사용하여 클래스의 속성을 캡슐화하고, 이를 통해 정보 은닉(information hiding)을 구현하는 방법
  • property는 클래스 속성에 대한 접근자와 설정자를 정의하는데 사용되며, 이를 통해 속성에 대한 접근 방식을 제어할 수 있다.
class PropertyClass :
    def __init__(self,name) :
        self._name = name
    @property
    def name(self) :
        return self._name
    @name.setter
    def name(self,value) :
        self._name = value

 

import pprint

pprint.pprint(PropertyClass.__dict__)

mappingproxy({'__dict__': <attribute '__dict__' of 'PropertyClass' objects>,
              '__doc__': None,
              '__init__': <function PropertyClass.__init__ at 0x7ee7f7002ef0>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'PropertyClass' objects>,
              'name': <property object at 0x7ee7f702f5b0>})

 

p = PropertyClass("은식이")
print(p.name)

 은식이

p.name = "금식이"
print(p.name)

 금식이

print(p.__dict__)
print(p._name)
p._name = "동식이"
print(p.name)

{'_name': '금식이'}
금식이
동식이

6. 상속(Inheritance)

6.1 상속
 

.1.1 상속 시 초기화 메서드 처리

  • 부모 클래스의 초기화 모듈을 이용(이전의 클래스를 재활용할 수 있는 속성)
  • 상속하는 클래스는 부모 클래스(슈퍼 클래스), 상속 받는 클래스는 자식 클래스(서브 클래스)
class Parent :
    def __init__(self,name,age) :
        self.name = name
        self.age  = age

class Child(Parent) :
    pass

  

import pprint

pprint.pprint(Parent.__dict__)

mappingproxy({'__dict__': <attribute '__dict__' of 'Parent' objects>,
              '__doc__': None,
              '__init__': <function Parent.__init__ at 0x7ee7f7002320>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Parent' objects>})

import pprint

pprint.pprint(Child.__dict__)

mappingproxy({'__module__': '__main__', '__doc__': None})

 

c = Child("자식",13)
print(c)
print(c.__dict__)

<__main__.Child object at 0x7ee8280c1840>

{'name': '자식', 'age': 13}

6.1.2 Super class와 sub class 관계 이해하기

  • 상속관계 확인하기
class GrandParent :
    def __init__(self,name,age) :
        self.name = name
        self.age  = age

class Parent(GrandParent) :
    pass

class Child(Parent) :
    pass

 

print(GrandParent.__bases__)
print(Parent.__bases__)
print(Child.__bases__)

(<class 'object'>,)

(<class '__main__.GrandParent'>,)

(<class '__main__.Parent'>,)

 

print(issubclass(Parent, GrandParent))
print(issubclass(Child, Parent))
print(issubclass(Child, GrandParent))

True

True

True

 

6.1.3 상속에 따른 네임스페이스 검색

  • 상속에 따른 네임스페이스 검색
class A :
    A_cls = "A 클래스 속성"

class B(A) :
    pass

class C(A) :
    pass

 

print(B.A_cls)
print(C.A_cls)

A 클래스 속성

A 클래스 속성

 

B.A_cls = "B 클래스 속성"

print(B.A_cls)
print(C.A_cls)
print(A.A_cls)

B 클래스 속성

A 클래스 속성

A 클래스 속성

 

어딘가에 형태를 잡아두고 거기에 접근하도록 함(형태는 메모리에 공간을 잡을때 추가로 확보함)

네임스페이스 비교

 

import pprint

pprint.pprint(A.__dict__)

mappingproxy({'A_cls': 'A 클래스 속성',
              '__dict__': <attribute '__dict__' of 'A' objects>,
              '__doc__': None,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'A' objects>})

 

import pprint # 직접접근으로 우리만의 속성을 부여함

pprint.pprint(B.__dict__)

mappingproxy({'__module__': '__main__', '__doc__': None, 'A_cls': 'B 클래스 속성'})

 

import pprint # C의 경우 상속받고 아무 변경 작업도 안함

pprint.pprint(C.__dict__)

mappingproxy({'__module__': '__main__', '__doc__': None})

 

6.2 상속할 때 자식 클래스 초기화 기능 추가

  • __ init_subclass __ 클래스 메소드
help(object.__init_subclass__)

Help on built-in function __init_subclass__:

__init_subclass__(...) method of builtins.type instance
    This method is called when a class is subclassed.
    
    The default implementation does nothing. It may be
    overridden to extend subclasses.

 

class Super :
    def __init_subclass__(cls,name) :
        print(type(cls),cls)
        cls.name = name


class Sub(Super, name="sub") :
    pass

print(Sub.name)

<class 'type'> <class '__main__.Sub'>

sub

 

네임스페이스 비교

import pprint

pprint.pprint(Super.__dict__)

mappingproxy({'__dict__': <attribute '__dict__' of 'Super' objects>,
              '__doc__': None,
              '__init_subclass__': <classmethod(<function Super.__init_subclass__ at 0x7ee7f7049000>)>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Super' objects>})

 

import pprint

pprint.pprint(Sub.__dict__)

 mappingproxy({'__module__': '__main__', '__doc__': None, 'name': 'sub'})

 

6.3 다중 상속(Multiple Inheritance)

6.3.1 다중상속 클래스 읽는 순서

  • 다중 상속 Class 정의 및 읽는 순서 확인하기
class Parent1 :
    def __init__(self,name) :
        print(" Parent1 ")
        self.name = name

class Parent2 :
     def __init__(self,name) :
        print(" Parent2 ")
        self.name = name

class Child(Parent1, Parent2) :
    pass

 

네임스페이스

  • mro - 메소드 해결하는 순서
import pprint

pprint.pprint(Child.mro())

[<class '__main__.Child'>,
 <class '__main__.Parent1'>,
 <class '__main__.Parent2'>,
 <class 'object'>]

 

  • 두개의 상속을 받았을 때 부모클래스가 동일한 이름을 가진 경우1에 Init인지 2의 init인지 확인하기 위해 순서를 정해놓은것(먼저 나온것을 기준으로) 
c = Child("다중상속")

  Parent1

  • 다른 부모 클래스의 __ init __ 메소드를 사용하고 싶을 경우
class Parent1 :
    def __init__(self,name) :
        print(" Parent1 ")
        self.name = name

class Parent2 :
     def __init__(self,name,age) :
        print(" Parent2 ")
        self.name = name
        self.age = age

class Child2(Parent1, Parent2) :

    def __init__(self,name, age=None) :
        if age is None :
            super().__init__(name) # super는 parent1일때만 사용
        else :
            Parent2.__init__(self,name,age) # 다른 클래스들은 직접 사용

 

c1 = Child2("다중상속")
print(c1.__dict__)

Parent1

{'name': '다중상속'}

 

c2 = Child2("다중상속", 33)
print(c2.__dict__)

Parent2

{'name': '다중상속', 'age': 33}

6.4 super 클래스 이해하기

  • super 클래스 이해하기
class A :
    A_cls = " AAA "

class B(A) :
    A_cls = " BBB "

print(super(B,B()).A_cls)

 AAA

 

class A :
    def __init__(self,name) :
        self.name = name

class B(A) :
     def __init__(self,name,age) :
        super().__init__(name)
        self.age = age

b = B("슈퍼우먼", 27)
print(b.__dict__)

{'name': '슈퍼우먼', 'age': 27}

  

두 개를 상속 받았을때

class A :
    def __init__(self,name) :
        print('class A')
        self.name = name

class B :
    def __init__(self,name,age) :
        print('class B')
        self.name = name
        self.age = age

class C(A,B) :
     def __init__(self,name,age=None) :
        print('class C')
        super().__init__(name)
        if age :
            C.mro()[2].__init__(self,name,age)


c = C("슈퍼우먼", 27)
print(c.__dict__)

c2 = C("슈퍼맨")
print(c2.__dict__)

class C
class A
class B
{'name': '슈퍼우먼', 'age': 27}
class C
class A
{'name': '슈퍼맨'}

6.5 Mixin 패턴 이해하기

6.5.1 메서드만 처리하는 Mixin 클래스 정의

  • Mixin 클래스 하나를 상속처리
  • Mixin 클래스는 이름에 Mixin을 붙이는것이 관례이다.
import operator as op

class OpMixin :

    def aroper(self,op_code) :
        return {'+':op.add,
                '*':op.mul}[op_code]  \
               (self.x, self.y if type(self.y) not in [str,list, tuple]  \
                               else  self.y if op_code != "*" else len(self.y))

 

class Num(OpMixin) :
    def __init__(self, x,y) :
        self.x = x
        self.y = y

 

class STR(OpMixin) :
    def __init__(self, x,y) :
        self.x = x
        self.y = y

 

class LIST(OpMixin) :
    def __init__(self, x,y) :
        self.x = x
        self.y = y

 

n = Num(5,6)

print(n.aroper("+"))
print(n.aroper("*"))

 11

30

 

s = STR("Hello","World")

print(s.aroper("+"))
print(s.aroper("*"))

HelloWorld
HelloHelloHelloHelloHello

 

l = LIST([1,2,3,4],[6,7])

print(l.aroper("+"))
print(l.aroper("*"))

[1, 2, 3, 4, 6, 7]
[1, 2, 3, 4, 1, 2, 3, 4] # 두개 존재하니까 곱하기해서 앞 리스트 두배

 

6.5.2 Mixin 다중 상속 시 주의할 사항

  • 여러 Mixin 클래스에 동일한 메소드
class AMixin :
    def method(self) :
        return "A Mixin"

class BMixin :
    def method(self) :
        return "B Mixin"

 

class A(AMixin, BMixin) : # 기본적으로 왼쪽에 있는것이 먼저 출력됨
    pass

 

a = A()
print(a.method())

A Mixin

 

class AB(AMixin, BMixin) :
    def __init__(self,code) :
        self.code = code

    def method(self) :
        if self.code == "B" :
            return BMixin.method(self)
        else :
            return AMixin.method(self)

 

ab = AB("B")
print(ab.method())

B Mixin

  • Mixin 클래스 다중상속 처리: 다른 메소드 이름
  • 따로 지정을 하지 않아도 큰 문제가 되지 않음
class AMixin :
    def getname(self) :
        return self.name

class BMixin :
    def getage(self) :
        return self.age

 

class AB(AMixin, BMixin) :
    def __init__(self,name,age) :
        self.name = name
        self.age = age

 

ab = AB("다중상속",777)
print(ab.getname())
print(ab.getage())

다중상속

777

 

7. 다형성(Polymorphism)

7.1 다형성이란

 다형성(Polymorphism)은 프로그래밍 언어의 요소들이 다양한 자료형에 속하면서도 일관된 인터페이스를 통해 처리될 수 있는 성질을 말한다. 특히 객체지향 프로그래밍(OOP)에서는 상속을 통해 부모 클래스와 자식 클래스가 동일한 메서드를 구현하고 이를 통해 다양한 객체를 일관되게 다룰 수 있다.

다형성의 특징

  1. 자료형의 유연성: 프로그래밍 언어의 각 요소(상수, 변수, 식, 객체, 함수, 메서드 등)가 다양한 자료형에 속하는 것이 허용된다.
  2. 다양한 구현: 상속 관계에 있는 클래스들이 동일한 메서드를 각각의 방식으로 구현할 수 있다.

다형성과 단형성

  • 다형성: 각 요소가 여러 자료형에 속할 수 있는 성질.
  • 단형성: 각 요소가 한 가지 형태만 가지는 성질.

다형성의 유형

  1. 오버라이딩(Overriding)
    • 부모 클래스의 메서드를 자식 클래스에서 재정의하여 사용
    • 자식 클래스의 메서드가 부모 클래스의 메서드를 대체

 2. 오버로딩(Overloading)

  • 동일한 이름의 메서드를 여러 개 정의하여 다양한 인자와 함께 사용할 수 있게 한다.
  • 파이썬은 메서드 오버로딩을 지원하지 않지만, 특정 모듈을 사용하여 구현할 수 있다. 

파이썬은 이름으로만 관리하므로 동일한 기능을 여러 개로 분리할 수 없지만 특정 모듈을 사용해서 오버로딩이 가능하다. (파이썬에서는 기본적으로 오버라이딩만 한다 생각하면 된다)

  •  
7.2  메소드 오버라이딩(Overriding)
  • 부모 클래스의 메소드를 오버라이딩
class Parent1 :
    def __init__(self,name) :
        print(" Parent1 ")
        self.name = name
    def getname(self) :
        return self.name

class Parent2 :
    def __init__(self,age) :
        print(" Parent2 ")
        self.age = age
    def getage(self) :
        return self.age

 

class Child(Parent1, Parent2) :

    def __init__(self,name, age=None) :
        super().__init__(name)
        if age is not None :
            Parent2.__init__(self,age)

    def getname(self) :
        return "child " + self.name
    def getage(self) :
        return "child " + str(self.age)

 

c = Child("오버라이딩", 33)

Parent1

Parent2

 

print(c.getname())
print(c.getage())

child 오버라이딩

child 33

 

print(Parent1.getname(c))
print(Parent2.getage(c))

오버라이딩

33

7.3 메서드 오버로딩(Overloading)

  • overload 모듈을 이용
  • 파이썬에서는 기본적으로 안됨으로 지원하는 라이브러리를 pip로 설치함
  • 코렙은 리눅스 위에서 동작함으로 터미널을 써야하는데, 터미널을 쓸 수 없으므로 !를 사용해서 pip 설치를 해줌
  • 오버로드가 안되는 것을 내부함수를 집어 넣음으로서 오버로딩된 결과랑 비슷하게 표현하는것(안되는것을 되게 하니까 빙빙 돌아서 작성한것이다)
!pip install --upgrade overload

 

from overload import overload

class A :
    @overload
    def method(self) :
        print(" no args method ")

    @method.add # 메소드 안에 add라는 함수를 내부 함수로 집어넣은것
    def method(self, x) :
        print(" one args method "+ x)

    @method.add
    def method(self, x,y) :
        print(" two args method "+ x,y)

 

a = A()

a.method()
a.method("hello")
a.method("hello","world")
a.method("hello","world","gays")

no args method 
one args method hello
two args method hello world

TypeError: invalid call argument(s) #3개는 메소드 구현을 안해놓았기 때문에 오류 발생함

 

import pprint

pprint.pprint(A.__dict__)

mappingproxy({'__dict__': <attribute '__dict__' of 'A' objects>,
              '__doc__': None,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'A' objects>,
              'method': <function A.method at 0x7ee7f70b69e0>}) 

 

print(type(A.method))
pprint.pprint(A.method.__dict__)

<class 'function'>
{'__wrapped__': <function A.method at 0x7ee7f70b6200>,
 'add': <function overload.<locals>.add at 0x7ee7f70b6a70>} 

 

7.4 연산자 오버로딩(Operator Overloading)

  • 연산자 오버로딩 이해하기
class Person :
    def __init__(self,name,age) :
        self.name = name
        self.age = age

class Parent(Person) :
    def __init__(self,name,age) :
        print(" Parent class ")
        self.name = name
        self.age  = age

class Child(Person) :
     def __init__(self,name,age) :
        print(" Child class ")
        self.name = name
        self.age  = age

 

p = Parent("해리",45)
c = Child("론",17)

Parent class

Child class

 

class Person :
    def __init__(self,name,age) :
        print(" Person class ")
        self.name = name
        self.age = age

class Parent(Person) :
    def __init__(self,name,age,pa_code) :
        super().__init__(name,age)
        self.pa_code = pa_code

    def ischild(self,child) :
        return True if self is child.pa_id else False

class Child(Person) :
    def __init__(self,name,age,pa_code,pa_id) :
        super().__init__(name,age)
        self.pa_code = pa_code
        self.pa_id = pa_id

    def isparent(self, parent) :
        return True if parent is self.pa_id else False

 

p = Parent("해리아빠",56,"p")
c = Child('해리',19,"c",p)

print(p.ischild(c))
print(c.isparent(p))

 Person class 
 Person class 
True
True

 

class Float_(float):
  def __init__(self, value):
      self.value = float(value)

  def __add__(self, value):
    return Float_(self.value + float(value)) # float가 바로 리턴

 

v = Float_(100.6)
print(v.value)
print(v+100)
print(type(v+100))

100.6
200.6
<class '__main__.Float_'>

 

float 클래스 상속받지 않고 진행

 

class Float_:
  def __init__(self, value):
      self.value = float(value)

  def __add__(self, value):
    return Float_(self.value + float(value)).value # 값을 리턴

 

v = Float_(100.6)
print(v.value)
print(v+100)
print(type(v+100))

100.6
200.6
<class 'float'>

7.5 덕 타이핑(Duck typing) 패턴 이해하기

  • 함수 내에서 인터페이스 처리
  • 파이썬과 같은 동적 타입 언어에서 사용되는 개념으로, 객체의 실제 타입보다는 객체가 수행할 수 있는 메서드나 속성에 의해 객체의 타입을 결정하는 패턴

 덕타이핑의 유래

"If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck."

 

떤 객체가 특정 타입의 객체처럼 행동한다면, 그 객체를 그 타입으로 간주할 수 있다는 의미

덕 타이핑은 인터페이스가 아닌 객체의 행위를 중심으로 타입을 판단하는 방식이다.

 

덕 타이핑의 특징

  1. 유연성: 덕 타이핑은 객체의 타입을 명시적으로 지정하지 않아도 되기 때문에 코드의 유연성이 높다.
  2. 재사용성: 동일한 인터페이스를 구현하는 여러 객체를 쉽게 교체하여 사용할 수 있다.
  3. 단순함: 객체가 필요한 메서드나 속성을 구현하기만 하면 되므로, 복잡한 상속 구조 없이도 다형성을 구현할 수 있다.
class Duck :
    def say(self) :
        return "quack quack"

class Person :
    def say(self) :
        return "Hello !"

 

def say(obj) :
    return obj.say()

 

클래스간의 관계는 상관없이, 오브젝트가 뭐가 들어갔냐에 따라서 동작 결과가 바뀐다.

 

d = Duck()
p = Person()

print(say(d))
print(say(p))

quack quack

Hello !

 

  • class내에서 인터페이스 제공
class Duck :
    def say(self) :
        return "quack quack"

class Person :
    def say(self) :
        return "Hello !"

 

class Say :
    @staticmethod
    def say(obj) :
        return obj.say()

 

d = Duck()
p = Person()

print(Say.say(d))
print(Say.say(p))

quack quack

Hello !

 

8. 메타 클래스(Meta Class)

파이썬에서 메타 클래스는 클래스의 클래스를 정의하는 도구로, 클래스를 생성하는 방식을 제어할 수 있게 합니다. 즉, 메타 클래스는 클래스가 생성될 때 그 동작을 수정하거나 확장하는 데 사용됩니다. 기본 메타 클래스는 type이며, 이를 확장하여 커스터마이징할 수 있습니다.

 

메타 클래스의 활용 이유

  1. 클래스 커스터마이징: 클래스 생성 시 특정 속성을 자동으로 추가하거나 수정할 수 있습니다.
  2. 유효성 검사: 클래스가 생성될 때 특정 조건을 검사하고, 조건에 맞지 않으면 예외를 발생시킬 수 있습니다.
  3. 패턴 구현: 싱글톤 패턴 등 특정 디자인 패턴을 구현할 수 있습니다.

 

8.1 메타 클래스와 클래스 정의로 클래스 만들고 비교하기
 

 

8.1.1 파이썬에서 메타 클래스로 클래스가 만들어지는 순서

  1. 적절한 클래스 결정
  2. 클래스 네임스페이스 준비
  3. 클래스의 본체 실행
  4. 클래스 객체를 생성하여 반환값으로 제공
  • 메타클래스로 클래스 생성하기
import pprint

namespace = { 'name' : "메타클래스로 클래스 생성"}
bases = (object,)
classname = "Klass"

Klass = type(classname, bases, namespace)

 

print(type(Klass))
print(Klass)
pprint.pprint(Klass.__dict__)

<class 'type'>
<class '__main__.Klass'>
mappingproxy({'__dict__': <attribute '__dict__' of 'Klass' objects>,
              '__doc__': None,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Klass' objects>,
              'name': '메타클래스로 클래스 생성'})

 

메타 클래스가 아닌 실제로 클래스를 만드는 경우

 

class Klass :
    name = " 클래스 정의문으로 클래스 생성"

print(type(Klass))
print(Klass)
pprint.pprint(Klass.__dict__)

<class 'type'>
<class '__main__.Klass'>
mappingproxy({'__dict__': <attribute '__dict__' of 'Klass' objects>,
              '__doc__': None,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Klass' objects>,
              'name': ' 클래스 정의문으로 클래스 생성'})

 

 

내부적으로는 그냥 만드는거나, 메타 클래스로 만드는 것이나 결과는 똑같은 것을 확인하였다.

 

8.1.2 클래스 상속과 메타 클래스의 관계

  • 사용자 메타클래스로 변경하기
class MyMeta(type) :
    pass

class Klass(metaclass=MyMeta) :
    pass

 

print(Klass)
print(Klass.__class__)

<class '__main__.Klass'>

<class '__main__.MyMeta'>

 

9. 연관관계(Association)

 

9.1 연관관계

  • 구성관계 (composition)
  • 구성관계는 클래스가 다른 클래스를 소유하며, 포함된 클래스의 생명주기가 포함하는 클래스에 의해 결정되는 경우를 의미
  • 포함하는 클래스가 삭제되면 포함된 클래스도 함께 삭제된다.
  • 강한 결합관계이다.
class Salary:
    def __init__(self,pay):
        self.pay=pay

    def get_total(self): # 월급 1년 받을시 얼마가 되는가
        return (self.pay*12)

 

class Employee:
    def __init__(self,pay,bonus):
        self.pay=pay
        self.bonus=bonus
        self.obj_salary=Salary(self.pay) # Salary 객체를 Employee 객체 내에서 생성

    def annual_salary(self):
        return "Total: "  +  str(self.obj_salary.get_total()+self.bonus)

 

obj_emp=Employee(200,10)
print (obj_emp.annual_salary())

Total: 2410

 

mployee 클래스는 Salary 객체를 생성하고 포함하는데, Salary 객체는 Employee 객체의 생명주기에 따라 존재하며, Employee 객체가 삭제되면 Salary 객체도 함께 삭제된다.  

 

  • 집합 관계(Aggregation)
  • 집합관계는 클래스가 다른 클래스를 포함하지만, 포함된 클래스의 생명주기가 포함하는 클래스와 독립적인 경우를 의미
  • 포함된 클래스는 포함하는 클래스와 독립적으로 존재할 수 있다.
  • 약한 결합 관계이다.
class Salary:
    def __init__(self,pay):
        self.pay=pay

    def get_total(self):
        return (self.pay*12)

 

class Employee:
    def __init__(self,pay,bonus):
        self.pay=pay
        self.bonus=bonus

    def annual_salary(self):
        return "Total: "  +  str(self.pay.get_total()+self.bonus)

 

obj_sal=Salary(200)
obj_emp=Employee(obj_sal,10)
print (obj_emp.annual_salary())

Total: 2410

 

Employee 클래스는 Salary 객체를 생성하지 않고, 외부에서 전달받는다. Salary 객체는 Employee 객체와 독립적으로 존재할 수 있으며, Employee 객체가 삭제되더라도 Salary 객체는 계속 존재할 수 있다.

 

del obj_emp
print(obj_sal)

<__main__.Salary object at 0x7ee7f70ab6d0>   # Salary 객체가 그대로 살아있는것을 알 수 있다.

 

 

 

9.2 위임 패턴 처리

  • 위임 패턴은 객체 지향 프로그래밍에서 클래스가 다른 클래스의 기능을 사용하기 위해 그 클래스의 인스턴스를 포함하는 패턴이다.
  • 코드의 재사용성을 높이고, 기능의 분리를 명확하게 할 수 있는 장점이 있다.
  • 위임 메소드를 사용하기
class Person :
    def __init__(self,name,age) :
        self.name = name
        self.age  = age
 
class Student :
    def __init__(self, name,age,college) :
        self.person = Person(name,age)
        self.college = college

 

s = Student("연관",22,"숭실대")
print(s.__dict__)

{'person': <__main__.Person object at 0x7ee7f70aada0>, 'college': '숭실대'}  

# Student 클래스는 Person 클래스를 포함하여 초기화  한다. __dict__ 속성을 출력하면 person 속성이 Person 객체를 가리키는 것을 확인할 수 있다.

 

위임 매서드 사용하기

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

    def getname(self) :
        return self.name
    def getage(self) :
        return self.age

 

class Student :
    def __init__(self, name,age,college) :
        self.person = Person(name,age)
        self.college = college

    def getname(self) :
        return self.person.getname() # 위임 메서드

    def getage(self) : 
        return self.person.getage() # 위임 메서드

 

s = Student("위임",22,"숭실대")
print(s.getname())
print(s.getage())

위임

22

# Student 클래스는 Person 클래스의 메서드인 getname과 getage를 위임하여 호출한다.

 

추가 위임 예시

  • 위임 패턴으로, 여러 클래스에서 공통적으로 사용되는 기능을 하나의 클래스로 모듈화 하여 다른 클래스에서 이를 위임받아 사용할 수 있다.
class Engine:
    def start(self):
        return "Engine started"

    def stop(self):
        return "Engine stopped"

class Car:
    def __init__(self):
        self.engine = Engine()

    def start_engine(self):
        return self.engine.start()  # 위임 메서드

    def stop_engine(self):
        return self.engine.stop()  # 위임 메서드

my_car = Car()
print(my_car.start_engine())  
print(my_car.stop_engine())  

Engine started

ngine stopped

 

 

# Car 클래스는 Engine 클래스의 기능을 위임받아 사용

# Car 클래스는 Engine 클래스의 메서드를 직접 호출하여 엔진을 시작하고 정지할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

728x90

'PYTHON-BACK' 카테고리의 다른 글

#파이썬 기초 10일차_1  (0) 2024.07.10
#파이썬 기초 9일차_2  (0) 2024.07.09
#파이썬 기초 8일차_2  (0) 2024.07.08
#파이썬 기초 8일차_1  (1) 2024.07.08
#파이썬 기초 7일차_2  (1) 2024.07.05