5.객체지향 설계
클래스와 객체
객체지향 프로그래밍의 원리
디자인 패턴
원을 나타내는 객체를 정의하고 싶다고 가정해보자. collections 모듈의네임드 튜플을 사용했던 것을 기억할 것이다.('2.3.3 네임드 튜플')
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Abstract_Data_Type> python
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 19:29:22) [MSC v.1916 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import collections
>>> circle = collections.namedtuple("Circle", "x y radius")
>>> circle
<class '__main__.Circle'>
>>> circle = circle(13,84,9)
>>> circle
Circle(x=13, y=84, radius=9)
하지만 이코드에서는 고려하지 않은 점이 많다. 첫째, 사용자가 원의 반지름(radius)을 입력할 때, 음수 등 유효하지 않은 값을 입력할 수도 있다. 둘째, 코드에서 원 넓이(area)와 둘레(perimeter)를 구하고 싶다면 어떻게 할까?
첫째 문제의 경우, 객체를 만들 때 유효성 검사를 할 수 없다는 것은 순수한 절차적 프로그래밍 방식의 매우 좋지 않은 측면임을 알 수 있다. 잘못된 입력에 대해 많은 예외 처리가 있다고 하더라도, 실제 목적에 맞게 유효성 검증을 할 수 없는 입력 데이터가 존재할 수 있다. 이 예에서 네임드 튜플 대신 리스트를 골랐다고 상상해보자. 리스트의 정렬 속성은 어떻게 다뤄야 할까?
이 예에서 배울 수 있는 점은 명확하다. 오직 우리가 기대하는 속성만 가진 객체를 만들어야 한다. 즉, 데이터를 패키지화하고, 메서드를 제한해야 한다. 이것이 바로 객체지향 프로그래밍이다. 이 예에서는 원을 나타낼 자신만의 고유한 데이터 타입, 즉 클래스를 만들어야 한다.
5.1 클래스와 객체
클래스(class)는 사전에 정의된 특별한 데이터와 메서드의 집합이다. 클래스에 선언된 모양 그대로 생성된 실체를 객체(Object)라고 한다. 객체가 소프트웨어에 실체화될 떄(메모리에 할당되어 사용될 떄), 이 실체를 인스턴스(instance)라고 한다. 객체는 인스턴스를 포함할 수 있으며, 포괄적인 의미를 지닌다. 파이썬에서 가장 간단한 형태의 클래스는 다음과 같다.
class ClassName:
# 문장1
# ...
# 문장 n
pass
>>> x = ClassName() 클래스 정의에 따라 인스턴스 생성
>>> x
5.1.1 클래스 인스턴스 생성
클래스 인스턴스 생성(class instantiation)은 함수 표기법을 사용하여 초기 상태의 객체를 생성하는 일이다. 인스턴스 생성작업은 어떤 특징을 가진 빈 객체를 만드는 것이다. (여러 범위의 ) 여러 이름을 같은 객체에 바인딩(binding)(또는 에일리어싱 aliasing) 할 수 있다. Hello 라는 클래스가 있다고 하자. 그러면 Hello()를 호출하여 객체를 생성하는데, 이떄 Hello()를 생성자(constructor)라고 한다. 생성자를 호출하면 Hello._new_() 라는 특수 메서드가 호출되어 객체가 할당되고 그 다음 Hello.__init__()메서드가 객체를 초기화한다.
속성
객체에는 데이터(data)와 메서드(method)로 이루어지는 클래스 속성(attribute)이 있다. 메서드 속성은 함수인데, 그 첫 번째 인수는 호출된 인스턴스 자신이다.(파이썬에서는 이를 셀프(self)라고 한다. )
속성은 점(.)뒤에 나오는 모든 이름이다. 모듈 내 모든 이름의 참조는 속성 참조다. 모듈명.함수명과 같은 표현식에서 모듈명은 모듈 객체이고, 함수명은 객체의 속성 중 하나다. 속성은 읽기 전용일 수도 있고 쓰기 가능할 수도 있다. 쓰기 가능한 속성은 del문으로 삭제할 수 있다.
네임스페이스
네임스페이스(namespace)는 이름을 객체로 매핑(mapping)하는 것이다. 대부분 네임스페이스는 파이썬 딕셔너리로 구현되어 있다. 네임스페이스의 예로는 내장된 이름 셋, 모듈의 전역이름, 함수의 지역 이름 등이 있다.
스크립트 파일이나 대화식 인터프리터의 최상위 호출에 의해 실행되는 명령문은 __main__ 이라는 모듈의 일부로 간주되어, 고유의 전역 네임스페이스를 갖는다.
스코프
스코프(scope)는 네임스페이스에 직접 접근할 수 있는 파이썬 프로그램의 텍스트영역(textual region)이다. 스코프는 정적으로 결정되지만, 동적으로 사용된다. 즉 스코프는 텍스트에 따라 결정된다. 즉 한 모듈에 정의된 함수의 전역 스코프는 해당 모듈의 네임스페이스다. 클래스 정의가 실행되면, 새로운 네임스페이스가 만들어지고, 지역 스코프로 사용된다.
5.2 객체지향 프로그래밍의 원리
5.2.1 특수화
**특수화**는 슈퍼(super) 클래스(부모(parent) 또는 베이스(base) 클래스라고도 한다)의 모든 속성을 상속(inheritance)하여 새 클래스를 만드는 절차다. 모든 메서드는 서브(sub)클래스(자식 클래스)에서 재정의(override), 재구현(re-implemented)될 수 있다. (파이썬에서 모든 메서드는 가상(virtual)이다.) 상속은 is-a관계다. 사람클래스와 이를 상속 받는 학생 클래스가 있다고 하자. 이 때 "모든 학생은 사람이다"라는 명제가 성립하며 이것이 is-a 관계다. 반대로 "모든 사람은 학생이다"는 성립하지 않는다. 구글 파이썬 가이드에서는 한 클래스가 다른 클래스를 상속 받지 않으면, 파이썬의 최상위 클래스인 object를 명시적으로 표기하는 것을 권장한다.
즉 좋은 예는 다음과 같다.
class SampleClass(object):
pass
class OuterClass(object):
class InnerClass(object):
pass
class ChildClass(ParentClass):
"""부모 클래스 상속"""
반면 다음은 나쁜 예이다.
class SampleClass:
pass
class OuterClass:
class InnerClass:
pass
5.2.2 다형성
**다형성(polymorphism)**(또는 동적 메서드 바인딩)은 메서드가 서브 클래스 내에서 재정의 될 수 있다는 원리다. 즉, 서브 클래스 객체에서 슈퍼 클래스와 동명의 메서드를 호출하면, 파이썬은 서브 클래스에 정의된 메서드를 사용한다는 뜻이다. 슈퍼 클래스의 메서드를 호출해야 한다면, 내장된 super() 메서드를 사용하여 쉽게 호출할 수 있다.
예를 들어 파이썬에서 사용자 정의 클래스의 모든 객체는 기본적으로 **해시가능(hashable)**하다. 객체가 해시 가능하다는 것은 hash() 속성을 호출할 수 있다는 뜻이며 불변 객체임을 의미한다. 다음 예제를 살펴보자.
class Symbol(object):
def __init__(self,value):
self.value = value
if __name__ == "__main__":
x = Symbol("Py")
y = Symbol("Py")
symbols = set()
symbols.add(x)
symbols.add(y)
print(x is y )
print( x==y)
print(len(symbols))
FalseFalse
두 변수 x,y의 참조가 다르므로 첫 번째 결과(x is y)는 예상대로 False가 나왔다. 그런데, x,y의 값이 같으니 두 번째 조건(x == y)은 True가 되어야 할 것 같지만 결과는 False다. 세 번째 결과 역시 셋은 중복 항목이 없으므로 길이가 1이 나와야 할 것 같지만 2가 나왔다.
두 번째와 세 번째 결과를 고치기 위해 객체의 비교를 담당하는 __eq__() 메서드를 재정의해보자.
class Symbol(object):
def __init__(self,value):
self.value = value
def __eq__(self, other):
if isinstance(self, other.__class__):
return self.value == other.value
else:
return NotImplemented
if __name__ == "__main__":
x = Symbol("Py")
y = Symbol("Py")
symbols = set()
symbols.add(x)
symbols.add(y)
print(x is y )
print( x==y)
print(len(symbols))
결과
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\2_hash_and_eq_NO.py
Traceback (most recent call last):
File ".\2_hash_and_eq_NO.py", line 19, in <module>
symbols.add(x)
TypeError: unhashable type: 'Symbol'
__eq__() 메서드를 재정의하자 Symbol 클래스가 해시 가능하지 않다고 (unhashable ) 에러가 발생한다. 객체가 해시 가능하지 않다는 것은 가변 객체임을 의미하는데, 셋은 불변 객체다. 에러를 고치기 위해 __hash__() 메서드를 추가한다.
class Symbol(object): def __init__(self,value): self.value = value def __eq__(self, other): if isinstance(self, other.__class__): return self.value == other.value else: return NotImplemented def __hash__(self): return hash(self.value)if __name__ == "__main__": x = Symbol("Py") y = Symbol("Py") symbols = set() symbols.add(x) symbols.add(y) print(x is y ) print( x==y) print(len(symbols))
결과
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\3_hash_and_eq_OK.pyFalseTrue1
이제 예상대로 결과가 나오는 것을 확인할 수 있다.
5.2.3 합성과 집합화
**합성(composition) 그리고 집합화(aggregation)**은 한 클래스에서 다른 클래스의 인스턴스 변수를 포함하는 것을 말하며, 클래스 간의 관계를 나타낸다. 파이썬의 모든 클래스는 상속을 사용한다(object 베이스 클래스로부터 상속받는다.) 대부분 클래스는 다양한 타입의 인스턴스 변수를 가지며, 합성과 집합화를 사용한다. 두 클래스 A,B가 있다고 가정한다. 합성은 A와 B가 강한 연관 관계를 맺으며, 강한 생명주기(strong lifecycle)를 갖는다. 즉, 의존성이 강하다. 예를 들어 집 클래스는 방 클래스를 갖는다. 집이 있으면 방(공간)이 있다.
집합화는 A와 B가 연관 관계가 있지만, 생명주기가 약하며 독립적이다. 예를 들어 학생 클래스는 미술, 음악 등의 과목 클래스를 갖는다. 한 학생은 미술, 음악 두 과목을 수강할 수 있고, 그중 한 과목 또는 두 과목 모두 수강하지 않을 수 있다.
5.2.4 클래스 예제
이번 장 앞에서 네임드 튜플로 구현한 원 클래스를 객체지향 설계로 다시 구현해보자. 즉 원의 데이터 컨테이너를 만들 것이다. 먼저, 일반적인 데이터와 메서드 속성을 가진 점(Point) 클래스를 구현하고, 그다음 상속을 사용하여 Circle 서브 클래스를 구현했다.
#클래스 예제import math"""hypot 쓰인 함수 설명math.hypot(*coordinates)유클리드 크기(norm) sqrt(sum(x**2 for x in coordinates))를 반환합니다. 원점에서 coordinates로 지정된 점까지의 벡터의 길이입니다.2차원 점 (x, y)의 경우, 피타고라스 정리를 사용하여 직각 삼각형의 빗변(hypotenuse)을 계산하는 것과 동등합니다, sqrt(x*x + y*y).버전 3.8에서 변경: n 차원 점에 대한 지원이 추가되었습니다. 이전에는, 2차원인 경우만 지원되었습니다.math.hypot(x, y) 가로 x 세로 y인 직각삼각형의 빗면의 유클리드 거리를 반환합니다. root(x^2 + y^2)x*x + y*y에서 루트씌웠다고 생각하면 됨"""#주클래스 점#서브클래스 원은 점클래스를 상속받는다.class Point(object): def __init__(self, x=0, y=0): self.x = x #데이터 속성(attribute) self.y = y def distance_From_origin(self): #메서드 속성 return math.hypot(self.x,self.y) def __eq__(self, other): return self.x == other.x and self.y == other.y def __repr__(self): return "point ({0.x!r}, {0.y!r}".format(self) def __str__(self): return "({0.x!r}, {0.y!r}".format(self)class Circle(Point): def __init__(self,radius,x=0, y=0): super().__init__(x,y) #생성 및 초기화 self.radius = radius def edge_distance_from_origin(self): return abs(self.distance_From_origin()- self.radius) def area(self): return math.pi*(self.radius**2) def circumference(self): return 2*math.pi*self.radius def __eq__(self,other): return self.radius == other.radius and super().__eq__(other) def __repr__(self): return "circle ({0.radius!r}, {0.x!r})".format(self) def __str__(self): return repr(self)
5.3 디자인패턴
**디자인 패턴(design pattern)**은 잘 설계된 구조의 형식적 정의를 소프트웨어 엔지니어링으로 옮긴 것이다. 다양한 디자인 패턴이 있고 이들을 사용하여 서로 다른 문제를 해결할 수 있다.
5.3.1 데커레이터 패턴
테커레이터 패턴은 @표기를 사용해 함수 또는 메서드의 변환을 우아하게 지정해주는 도구다. 데커레이터 패턴은 함수의 객체와 함수를 변경하는 다른 객체의 래핑(wrapping)을 허용한다. 구글 파이썬 스타일 가이드의 코드 예제를 살펴보자.
Class C(object): @my_decorator def method(self): #메서드 내용
위 코드가 뜻하는 바는 아래와 같다.
class C(object): def method(self): # 메서드 내용 method = my_decorator(method)
데커레이터를 사용하여 리스트에 임의의 값을 넣는 함수를 벤치마킹하는 코드 예제는 다음과 같다.
import random
import time
def benchmark(func):
def wrapper(*args, **kwargs):
t = time.perf_counter()
print("t",t)
res = func(*args, **kwargs)
print("{0} {1}".format(func.__name__, time.perf_counter()-t))
return res
return wrapper
#파이썬에서 코드 실행시간을 측정하는 방법을 찾아 테스트해봤습니다.
#파이썬 3.3 이상부터 perf_counter와 process_time을 사용할 수 있는데 차이점은 다음과 같습니다.
# perf_counter는 sleep 함수를 호출하여 대기한 시간을 포함하여 측정합니다.
# process_time는 실제로 연산하는데 걸린 시간만 측정합니다.
@benchmark
def random_tree(n):
temp = [ n for n in range(n)]
for i in range(n+1):
temp[random.choice(temp)] = random.choice(temp)
return temp
if __name__ == "__main__":
random_tree(10000)
결과
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\4_benchmark_decorator.py
t 0.2078034
random_tree 0.07035290000000002
파이썬에서 일반적으로 사용하는 데커레이터는 @classmethod와 @static-method가 있다. 이들은 각각 메서드를 클래스와 정적 메서드로 변환한다. 다음 코드에서 두 데커레이터의 차이점을 살펴보자. @classmethod는 첫 번째 인수로 클래스(cls)를 사용하고, @staticmethod는 첫 번째 인수에 self 혹은 cls가 없다. 클래스 내 변수에 접근하려면 @classmethod의 첫 번째 인수를 사용할 수 있다.
class A(object):
_hello = True
def foo(self, x):
print("foo{0} {1} 실행".format(self,x))
@classmethod
def class_foo(cls, x):
print("class_foo({0}, {1}) 실행: {2}".format(cls,x, cls._hello))
@staticmethod
def static_foo(x):
print("static_foo({0}) 실행".format(x))
if __name__ == "__main__":
a = A()
a.foo(1)
a.class_foo(2)
A.class_foo(2)
a.static_foo(3)
A.static_foo(3)
결과
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\5_class_and_static_decorator.pyfoo<__main__.A object at 0x019A42D0> 1 실행class_foo(<class '__main__.A'>, 2) 실행: Trueclass_foo(<class '__main__.A'>, 2) 실행: Truestatic_foo(3) 실행static_foo(3) 실행
5.3.2 옵서버 패턴
옵서버(observer) 패턴은 특정 값을 유지하는 핵심 객체를 갖고, 직렬화된 객체의 복사본을 생성하는 일부 옵서버(관찰자)가 있는 경우 유용하다. 즉, 객체와 일대다(one-to-many) 의존 관계에서 한 객체의 상태가 변경되면 그 객체에 종속된 모든 객체에 그 내용을 통지하여 자동으로 상태를 갱신하는 방식이다. 옵서버 패턴은 @property 데커레이터를 사용하여 구현할 수 있다. 예를 들어 속성(property)을 읽기 전용으로 설정하는 것과 같은 속성 접근을 제어할 수 있다, 속성은 접근자(access)나 getter/setter 메서드 대신 사용된다. 먼저 간단한 속성 예제를 살펴보자.
class C: def __init__(self,name): self._name = name @property def name(self): return self._name @name.setter def name(self, new_name): self._name = "{0} >> {1}".format(self._name, new_name)c= C("진")print(c._name)print(c.name)c.name="아스틴"print(c._name)
결과
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\test.py진진진 >> 아스틴
파이썬의 옵서버 패턴은 다른 컴퓨터 언어와조금 다른 방식으로 구현된다. 다음은 속성을 사용한 옵서버 패턴의 구현 내용과 예제다. [Dive Into Design Pattern](SourceMaking.com, 2018)을 참조했다. 유튜브 InfoQ 채널의 Tutorial: The Observer Pattern in Python도 참조했다.
class Subcriber(object): def __init__(self, name): self.name=name def update(self,message): print("{0}, {1}".format(self.name, message))class Publisher(object): def __init__(self): self.subscribers=set() def register(self,who): self.subscribers.add(who) def unregister(self, who): self.subscribers.discard(who) def dispatch(self,message): for subscriber in self.subscribers: subscriber.update(message)if __name__ == "__main__": pub = Publisher() astin = Subcriber("아스틴") james = Subcriber("제임스") jeff = Subcriber("제프") pub.register(astin) pub.register(james) pub.register(jeff) pub.dispatch("점심시간입니다.") pub.unregister(jeff) pub.dispatch("퇴근시간입니다.")
결과
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\6_observer_pattern_with_set.py아스틴, 점심시간입니다.제프, 점심시간입니다. 제임스, 점심시간입니다.아스틴, 퇴근시간입니다.제임스, 퇴근시간입니다.
Publisher 클래스에서 셋을 사용하여 옵서버 패턴을 구현해봤다. 다음코드에서는 딕셔너리를 사용해보자.
class SubscriberOne(object): def __init__(self,name): self.name=name def update(self,message): print("{0}, {1}".format(self.name, message))class SubscriberTwo(object): def __init__(self,name): self.name = name def receive(self,message): print("{0}, {1}".format(self.name, message))class Publisher(object): def __init__(self): self.subscribers = dict() def register(self,who,callback=None): if callback is None: callback = getattr(who,'update') self.subscribers[who] = callback def unregister(self,who): del self.subscribers[who] def dispatch(self,message): for subscriber,callback in self.subscribers.items(): callback(message)if __name__ == "__main__": pub = Publisher() astin = SubscriberOne("아스틴") james = SubscriberTwo("제임스") jeff = SubscriberOne("제프") pub.register(astin, astin.update) pub.register(james, james.receive) pub.register(jeff) pub.dispatch("점심시간입니다.") pub.unregister(jeff) pub.dispatch("퇴근시간입니다.")
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\7_observer_pattern_with_dict.py아스틴, 점심시간입니다.제임스, 점심시간입니다.제프, 점심시간입니다. 아스틴, 퇴근시간입니다.제임스, 퇴근시간입니다.
Subscriber 클래스, 즉 구독자의 형태가 다양하면 이전 코드보다 조금 더 유연하게 구현할 수 있다.(SubscriberOne 클래스, SubscriberTwo 클래스). 마지막으로 이벤트 기반의 옵서버 패턴을 살펴보자.
class Subscriber(object): def __init__(self,name): self.name = name def update(self,message): print("{0}, {1}".format(self.name, message))class Publisher(object): def __init__(self,events): self.subscribers = {event: dict() for event in events} print('__init__ events',events) def get_subscribers(self,event): return self.subscribers[event] def register(self,event,who,callback=None): if callback is None: callback = getattr(who, 'update') self.get_subscribers(event)[who] = callback print("event",event) print("who",who) print("callback",callback) print("self",self) def unregister(self, event, who): del self.get_subscribers(event)[who] def dispatch(self, event, message): for subscriber, callback in self.get_subscribers(event).items(): callback(message) if __name__ == "__main__": pub = Publisher(["점심", "퇴근"]) astin = Subscriber("아스틴") james = Subscriber("제임스") jeff = Subscriber("제프") pub.register("점심", astin) pub.register("퇴근",astin) pub.register("퇴근",james) pub.register("점심",jeff) pub.dispatch("점심", "점심시간입니다.") pub.dispatch("퇴근","저녁시간입니다.")
코드결과
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\8_observer_pattern_with_event.py
__init__ events ['점심', '퇴근']
event 점심
who <__main__.Subscriber object at 0x00A61D30>
callback <bound method Subscriber.update of <__main__.Subscriber object at 0x00A61D30>>
self <__main__.Publisher object at 0x00A61CD0>
event 퇴근
who <__main__.Subscriber object at 0x00A61D30>
callback <bound method Subscriber.update of <__main__.Subscriber object at 0x00A61D30>>
self <__main__.Publisher object at 0x00A61CD0>
event 퇴근
who <__main__.Subscriber object at 0x016D4690>
callback <bound method Subscriber.update of <__main__.Subscriber object at 0x016D4690>>
self <__main__.Publisher object at 0x00A61CD0>
event 점심
who <__main__.Subscriber object at 0x016D4670>
callback <bound method Subscriber.update of <__main__.Subscriber object at 0x016D4670>>
self <__main__.Publisher object at 0x00A61CD0>
아스틴, 점심시간입니다.
제프, 점심시간입니다.
아스틴, 저녁시간입니다.
제임스, 저녁시간입니다.
5.3.3 싱글턴 패턴
초기화된 객체의 인스턴스를 전역에서 사용하기 위해서는 싱글턴(singleton) 패턴을 사용한다. 이 객체의 인스턴스는 하나만 존재한다. 파이썬에는 private 접근 제한자가 없기 때문에 __name__() 클래스 메서드를 가지고 하나의 인스턴스만 생성되도록 구현해야 한다. 먼저 싱글턴 인스턴스가 생성되었는지 확인한다. (이미 싱글턴 인스턴스가 생성되었는데, 또 다시 생성을 시도했는지 확인한다). 싱글턴 인스턴스가 없다면, 슈퍼 클래스를 호출하여 싱글턴 인스턴스를 생성한다.
class SinEx:
_sing =None
def __new__(self, *args, **kwargs):
if not self._sing:
self._sing = super(SinEx, self).__new__(self, *args, **kwargs)
return self._sing
x = SinEx()
print("x",x)
y = SinEx()
print("x == y check: ",x==y)
print("y",y)
PS D:\Mastering-Python-Design-Patterns-Second-Edition\algo\Object_Design> python .\9_singleton.py
x <__main__.SinEx object at 0x016D42F0>
x == y check True
y <__main__.SinEx object at 0x016D42F0>
위 예제에서 두 객체의 주소는 같으므로, 두 객체는 같다. 사실 디자인 패턴에 대해서는 다양한 의견이 있다. 파이콘 스웨덴에서 발표한 파이썬 디자인 패턴에 대한 "Design Patterns in Python by Peter Ullrich"유튜브 영상도 참고할 만하다.
'언어 > 파이썬' 카테고리의 다른 글
[04] 파이썬 자료구조와 알고리즘-공부정리(추상 데이터 타입) (0) | 2022.01.08 |
---|---|
[06] 파이썬 자료구조와 알고리즘-공부정리(파이썬 고급주제) (0) | 2022.01.08 |
[04] 파이썬 자료구조와 알고리즘-공부정리(구조와 모듈) (0) | 2022.01.08 |
[03] 파이썬 자료구조와 알고리즘-공부정리(컬렉션 자료구조) (0) | 2022.01.08 |
[02] 파이썬 자료구조와 알고리즘-공부정리(내장시퀸스 타입) (0) | 2022.01.08 |