일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Python Code
- Generator
- Python
- DWG
- 컴퓨터의 구조
- 30. Substring with Concatenation of All Words
- Substring with Concatenation of All Words
- 109. Convert Sorted List to Binary Search Tree
- shiba
- 43. Multiply Strings
- 운영체제
- data science
- LeetCode
- t1
- attribute
- concurrency
- Class
- Python Implementation
- Regular Expression
- 파이썬
- Protocol
- Convert Sorted List to Binary Search Tree
- Decorator
- kaggle
- 프로그래머스
- 밴픽
- 시바견
- 315. Count of Smaller Numbers After Self
- 715. Range Module
- iterator
- Today
- Total
Scribbling
Python: Sequence Protocol 본문
To learn sequence protocol in Python, we create a custom vector class.
from array import array
import math
import reprlib
class Vector:
typecode = 'd'
def __init__(self, components):
self._components = array(self.typecode, components)
def __iter__(self):
return iter(self._components)
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __bool__(self):
return bool(abs(self))
@classmethod
def frombytes(cls, octests):
typecode = chr(octests[0])
memv = memoryview(octests[1:]).cast(typecode)
return cls(memv)
In Python, to create an object that works as a sequence, we don't need to inherit any classes. Instead, all we have to do is implement magic methods. This mechanism is called as "Duck Typing".
So, we only need to implement '__getitem__' magic method for our vector class to operate as a sequence.
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
msg = f'{cls.__name__} indices must be integers'
raise TypeError(msg)
Next, '__getattr__' magic method. How does Python Interpreter look for attributes? It first checks if the attribute is defined in the object. If there isn't, it searches for a class attribute. Again if there isn't, the interpreter searches super classes. If the interpreter fails to find the attribute in the above process, it then calls __getattr__ method.
shortcut_names = 'xyzt'
def __getattr__(self, name):
cls = type(self)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
return self._components[pos]
msg = f'{cls.__name__!r} object has no attribute {name:!r}'
raise AttributeError(msg)
v1 = Vector(range(1, 10))
print(v1.y)
v1.y = 3
print(v1.y)
The result shows that v1.y works fine. However, a code like "v1.y = 3" can be troublesome as our vector class is supposed to be immutable. Plus, it didn't even change the value of the vector v1! What happend?
It happens because of how python interpreter looks for an attribute. Python calls '__getattr__' method as the final step. In this example, however, "v1.y = 3" adds a 'y' attribute to the v1 object. And as v1 has a 'y' attribute, "v1.y" now does not work as intended.
To prevent such problem, we must add '__setattr__' magic method to our class. Remeber to implment '__setattr__' when you implement '__getattr__' to avoid such inconsistency.
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = 'can`t set attributes `a` to `z` in {cls_name!r}'
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__, attr_name=name)
raise AttributeError(msg)
super().__setattr__(name, value)
Full code:
from array import array
import math
import reprlib
import numbers
import functools
import operator
import itertools
class Vector:
typecode = 'd'
def __init__(self, components):
self._components = array(self.typecode, components)
def __iter__(self):
return iter(self._components)
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components))
def __eq__(self, other):
return len(self) == len(other) and all((a == b for a, b in zip(self, other)))
def __hash__(self):
# hashes = (hash(x) for x in self)
hashes = map(hash, self)
return functools.reduce(operator.xor, hashes, 0)
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __bool__(self):
return bool(abs(self))
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
msg = f'{cls.__name__!r} indices must be integers'
raise TypeError(msg)
shortcut_names = 'xyzt'
def __getattr__(self, name):
cls = type(self)
print(cls, name)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
return self._components[pos]
msg = f'{cls.__name__!r} object has no attribute {name!r}'
raise AttributeError(msg)
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = 'can`t set attributes `a` to `z` in {cls_name!r}'
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__, attr_name=name)
raise AttributeError(msg)
super().__setattr__(name, value)
def angle(self, n):
r = math.sqrt(sum(x * x for x in self[n:]))
a = math.atan2(r, self[n-1])
if (n == len(self) - 1) and (self[-1] < 0):
return math.pi * 2 - a
else:
return a
@property
def angles(self):
return [self.angle(n) for n in range(1, len(self))]
def __format__(self, fmt_spec=''):
if fmt_spec.endswith('h'):
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)], self.angles)
outer_fmt = '<{}>'
else:
coords = self
outer_fmt = '({})'
components = (format(c, fmt_spec) for c in coords)
return outer_fmt.format(','.join(components))
@classmethod
def frombytes(cls, octests):
typecode = chr(octests[0])
memv = memoryview(octests[1:]).cast(typecode)
return cls(memv)
'Computer Science > Python' 카테고리의 다른 글
Python: Interfaces (0) | 2022.04.18 |
---|---|
Python: ABC Class (0) | 2022.04.07 |
Python: Pythonic Object (0) | 2022.04.05 |
Python: Object References (0) | 2022.04.04 |
Python: Decorator & Closure (0) | 2022.03.29 |