본문 바로가기
코딩 연습/코딩배우기

파이썬 실천 기술 #06 - 내장함수와 특수메서드(스페셜 메서드)

by good4me 2021. 6. 12.

goodthings4me.tistory.com

 

효율적 개발로 이끄는 파이썬 실천 기술 - 내장함수와 특수메서드

(참고용으로 사용할 부분만 간략하게 정리)

내장 함수(embeded function)

  • 객체 타입 조사 : isinstance(), issubclass(), callable()
  • 객체 속성 관련 함수 : hasattr(), getattr(), setattr(), delattr()
  • 이터러블 객체 받는 함수 : zip(), sorted(), filter(), map(), all(), any()
# ininstance(), issubclass() - 동적 타입 판정

d = {}
print(isinstance(d, dict))
print(isinstance(d, (int, list, dict)))  # 튜플의 여러 클래스에서 동시 비교

print(issubclass(dict, object))
print(issubclass(bool, int))
print(issubclass(bool, (list, dict, int, float)))


[결과]
True
True
True
True
True

 

# 값을 꺼낼 때 dict 타입인지 체크
def get_value(obj, key):
    if not isinstance(obj, dict):  # obj가 dict 타입이 아니면
        raise ValueError
    return obj[key]

from collections import UserDict
# collections.UserDict는 사용자 정의 딕셔너리 객체를 생성하는 클래스

class MyDict(UserDict):
    pass

my_dict = MyDict()
my_dict['a'] = 1
print(my_dict['a'])

#get_value(my_dict, 'a')  # ValueError: , dict의 서브클래스 아니므로 에러 발생


[결과]
1

 

# callable() - 객체의 호출 가능 여부 판정
# 함수나 클래스, 메서드 등 ()를 붙여 호출할 수 있는 객체를 '호출 가능 객체(callable object)'라고 함
# 특수 메서드 __call__()을 가진 인스턴스도 ()를 붙여 호출 가능

class Threshold:
    def __init__(self, threshold):
        self.threshold = threshold
    
    def __call__(self, x):
        return self.threshold < x

threshold = Threshold(2)  # 인스턴스화 시 임계값 지정
print(threshold(3))  # __call__() 메서드가 호출됨
print(threshold(2))


[결과]
True
False

 

# hasattr() - 객체의 속성 유무 판정

def is_package(module_or_package):
    return hasattr(module_or_package, '__path__')  # __path__ 속성 가지는 패키지 여부 판단

import json, os
print(is_package(json))  # json은 패키지임
print(is_package(os))  # os는 패키지 아님


[결과]
True
False

 

# getattr(), setattr(),delattr() - 객체 속성 조작

class Mutable:
    def __init__(self, dic):
        for k, v in dic.items():
            setattr(self, str(k), v)  # key를 인스턴스 변수로 하여 값 셋팅

mu = Mutable({'a': 10, 'b': 20})
print(getattr(mu, 'a'), mu.a)  # getattr(mu, 'a') == mu.a

delattr(mu, 'b')  # 삭제됨
#print(mu.b)  # AttributeError: 'Mutable' object has no attribute 'b'

# getattr()은 인스턴스 메서드도 얻을 수 있다
strT = 'python'  # 문자열 객체의 인스턴스
ins_method = getattr(strT, 'upper')  # upper() 메서드
print(ins_method())
print(strT.upper())


[결과]
10 10
PYTHON
PYTHON

good4me.co.kr

# zip() - 다수의 이터러블 엘리먼트를 동시에 튜플로 반환(이터레이터 생성 내장함수)
x = [1, 2, 3]
y = [4, 5, 6, 7]
res = zip(x, y)  # 작은 쪽에 맞춰 반환
print(type(res), res)
print(list(res))  # 내용 확인 위해 리스트로 변환

# 긴쪽에 맞추기는 itertools.zip_longest() 사용
from itertools import zip_longest
print(list(zip_longest(x,y, fillvalue=0)))


[결과]
<class 'zip'> <zip object at 0x7efd9584d8c0>
[(1, 4), (2, 5), (3, 6)]
[(1, 4), (2, 5), (3, 6), (0, 7)]

 

# sorted() - 이터러블 엘리먼트 정렬, 새로운 객체 반환, 반환값은 리스트임

# 숫자, 문자가 섞여있는 경우, 인수 key에 인수를 하나만 얻는 함수 지정하여 각 엘리먼트를 비교할 값으로 반환하여 사용
x = ['1', '4', 3, 5, '2']
res = sorted(x, key=lambda v: int(v))  # reverse=True 인수 추가하여 역순 정렬도 가능
print(res)


[결과]
['1', '2', 3, '4', 5]

 

# filter() - 조건에 맞는 엘리먼트만을 포함한 이터레이터 반환, 인수를 하나만 받는 함수를 첫 번째 인수로 지정
x = (1, 4, 3, 5, 2)
res_fil = filter(lambda i: i > 3, x)
print(type(res_fil), res_fil)
print(list(res_fil))


# filter() 조건에 None 전달 시 참(True)이 되는 객체만 남김
x = (1, 0, None, 2, [], 'python')
print(list(filter(None, x)))  # filter(None, x)


[결과]
<class 'filter'> <filter object at 0x7efd95216910>
[4, 5]
[1, 2, 'python']

 

# map() - 이터러블의 모든 엘리먼트에 대해 같은 함수를 적용, 반환 결과는 이터레이터임
x = (1, 4, 3, 5, 2)
res = map(lambda i: i * 10, x)
print(type(res), res)
print(list(res))

# map() 인수로 여러 이터러블 객체를 받기, 첫 번째 인수 함수에 전달하는 인수의 개수와 이터러블 객체의 수는 일치해야 함
k = ('q', 'limit', 'page')
v = ('python', 10, 2)
res = map(lambda i, j: f'{i} = {j}', k, v)  # 인수의 개수 일치
print(list(res))

# join()과 조합해 문자열 작성
'?'+'&'.join(map(lambda i, j: f'{i}={j}', k, v))


[결과]
<class 'map'> <map object at 0x7efd94d05d10>
[10, 40, 30, 50, 20]
['q = python', 'limit = 10', 'page = 2']
?q=python&limit=10&page=2

 

# itemgetter() - sorted()와 조합 시 편리한 모듈 (key를 받고 딕셔너리에서 해당 키(들)에 대응하는 값을 찾아 반환)
from operator import itemgetter

d = {'word': 'python', 'count': 3}
fn = itemgetter('count')  # key가 되는 값 받고
print(fn(d))  # 딕셔너리 d에서 찾아 반환, d['conut']를 반환

# sorted()와 조합
counts = [{'word': 'python', 'count': 3},
          {'word': 'practice', 'count': 3},
          {'word': 'book', 'count': 2}]
res1 = sorted(counts, key=itemgetter('count'))
print(res1)
res2 = sorted(counts, key=itemgetter('count', 'word'))
print(res2)


[결과]
3
[{'word': 'book', 'count': 2}, {'word': 'python', 'count': 3}, {'word': 'practice', 'count': 3}]
[{'word': 'book', 'count': 2}, {'word': 'practice', 'count': 3}, {'word': 'python', 'count': 3}]

 

# all(), any() - 한 개의 이터러블을 받고, all()은 모든 엘리먼트가 참이면 True, any()는 참인 엘리먼트가 한 개 이상이면 True 반환

print(all([3, '1', 'book']), all([3, 0, 'book']))
print(any([3, None, '']), all([None, 0, {}]))


[결과]
True False
True False

 

 

특수 메서드(Special method)

  • 파이썬이 암묵적으로 호출하는 특수한 메서드(파이썬의 특징)
  • 메서드 이름 앞뒤로 언더스코어 두개(__) 붙음
  • 객체를 문자열로 표현 : str(), repr()
  • 객체를 논리값으로 평가 : bool()
  • 인스턴스를 함수처럼 다룸 : call()
  • 속성으로의 동적 접근 : setattr(), delattr(), getattr(), getattribute()
  • 이터러블 객체로서 동작 : iter(), next()
  • 컨테이너 객체로서 동작 : getitem(), setitem(), contains()
# __str__(), __repr__() - 객체의 이름(변수)를 입력하면 해당 객체의 문자열로 표현, 이 둘은 차이가 있다 
# 객체 이름만 입력 시 __repr__() 호출, print()에 인수로 전달 시 __str__() 호출
s = 'string'
s
# 'string'

print(s)
# string

 

# __repr__()은 디버그 등에 도움이 되는 정보 제공용 특수 메서드
# __str__()은 print(), str(), f'{}' 등에서 사용하는 사용자 친화적 문자열 반환하는 특수 메서드
# __str__()이 없으면 __repr__() 호출됨

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

    def __repr__(self):
        return f'Point({self.x}, {self.y})'

p = Point(2, 3)
print(p)


[결과]
Point(2, 3)

 

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

    def __str__(self):
        return f'({self.x}, {self.y})'

p = Point(2, 3)
print(p)


[결과]
(2, 3)

 

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

    def __str__(self):
        return f'({self.x}, {self.y})'

p = Point(2, 3)
p


[결과]
<__main__.Point at 0x7efd8f1b65d0>

 

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

    def __str__(self):
        return f'({self.x}, {self.y})'

    def __repr__(self):
        return f'Point({self.x}, {self.y})'

p = Point(2, 3)
p


[결과]

Point(2, 3)

 

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

    def __str__(self):
        return f'({self.x}, {self.y})'

    def __repr__(self):
        return f'Point({self.x}, {self.y})'

p = Point(2, 3)
print(p)


[결과]
(2, 3)

 

# __call__() - 이 메서드를 구현한 클래스에서는 인스턴스를 함수처럼 호출 가능, 인수턴스 상태 유지 가능(함수와의 차이점)

class Adder:
    def __init__(self):
        self._values = []
    
    def add(self, x):
        self._values.append(x)
    
    def get_values(self):
        return sum(self._values)

adder = Adder()
adder.add(10)
adder.add(5)
#adder()  # TypeError: 'Adder' object is not callable
adder.get_values()  # 결과 보는 메서드 호출


[결과]
15

 

class Adder:
    def __init__(self):
        self._values = []
    
    def add(self, x):
        self._values.append(x)
    
    def __call__(self):
        return sum(self._values)

adder = Adder()
adder.add(10)
adder.add(5)
adder()  # 함수처럼 호출


[결과]
15

 

# 함수 객체의 실체는 __call__()을 구현한 function 클래스의 인스턴스

def fn():
    return 1

print(type(fn))  # <class 'function'>
print(hasattr(fn, '__call__'))  # '__call__' 속성 존재 확인
dir(fn)


[결과]
<class 'function'>
True
['__annotations__',
 '__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__globals__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__kwdefaults__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']

 

# __setattr__() - 속성에 대입해 호출하는 특수 메서드, p.x = 1 처럼 __setattr__(self, x, 1) 대입

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __setattr__(self, name, value):
        if name not in('x', 'y'):
            raise AttributeError('Not allowed')
        super().__setattr__(name, value)  # 자신의 속성 호출로 무한루프에 빠지는 것 방지

p = Point(2, 3)
print(p.x)
print(p.y)
#p.z = 5  # AttributeError: Not allowed


[결과]
2
3

 

# __getattr__() - 속성에 접근해 호출

class Point:
    pass

p = Point()
p.__dict__  # {}
p.x = 1
p.__dict__  # {'x': 1}
p.y = 2
p.__dict__  # {'x': 1, 'y': 2}

# 인스턴스 이름공간에는 속성 __dict__ 에는 대입된 속성이 저장되어 있다
print(hasattr(p, '__dict__'))  # True
dir(p)


[결과]
True
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'x',
 'y']

 

# __delattr__() - 속성의 삭제 호출

class Point:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    
    def __delattr__(self, name):
        if name in('x', 'y'):
            raise AttributeError('Not allowed')
        super().__delattr__(name)

p = Point(1, 2, 3)
#del p.x  # AttributeError: Not allowed
del p.z
hasattr(p, 'z')


[결과]
False

 

 

이터러블

  • iter()를 구현한 객체
  • iter()의 반환값은 임의의 이터레이터

이터레이터

  • iter()와 next()를 구현한 객체
  • iter()의 반환값은 자신(self)
# __iter__() 
# 이터레이터 객체로 반환, 사용자 정의 클래스에서도 이 메서드를 구현해 이터러블로 사용 가능, for i in x: 에서 for 문은 x의 __iter__()를 호출해 그 반환값(이터레이터 객체)을 사용함

class Iterable:
    def __init__(self, num):
        self.num = num
    
    def __iter__(self):
        return iter(range(self.num))  # range() 반환 이터레이터

for i in Iterable(3):  # __iter__() 호출
    print(i)

print(iter(range(3)))


[결과]
0
1
2
<range_iterator object at 0x7efd92be97b0>

 

it = iter(range(10))
print(next(it))
next(it)


[결과]
0
1

 

# next() - 다음 엘리먼트 반환

class Reverser:
    def __init__(self, x):
        self.x = x
    
    def __iter__(self):
        return self
    
    def __next__(self):
        try:
            return self.x.pop()
        except IndexError:
            raise StopIteration()

[val for val in Reverser([3, 1, 6, 4])]


[결과]
[4, 6, 1, 3]

 

# __getitem__(), __setitem__() - 인덱스와 키를 통해 접근해 호출 (x[1], x['key] 등)

from collections import defaultdict  # 초기값은 int 타입 0

class CountDict:
    def __init__(self):
        self._data = {}
        self._get_count = defaultdict(int)
        self._set_count = defaultdict(int)
    
    def __getitem__(self, key):  # c['x'] 등 참조 시 호출
        self._get_count[key] += 1
        return self._data[key]
    
    def __setitem__(self, key, value):  # c['x'] = 1 등 대입 시 호출
        self._set_count[key] += 1
        self._data[key] = value
    
    @property
    def count(self):
        return{
            'set': list(self._set_count.items()),
            'get': list(self._get_count.items()),
        }

c = CountDict()
c['x'] = 10
c['x'] = 20
c['y'] = 30
c.count


[결과]
{'get': [], 'set': [('x', 2), ('y', 1)]}

 

[참고/출처] 효율적 개발로 이끄는 파이썬 실천 기술 - 파이썬에 숨겨진 힘을 이용해 개발 효율을 높이자!

 

댓글