[python] special method

hyunsooo·2021년 10월 20일
0

일반적으로 사용하는 str 함수는 정수나, 소수 등의 수를 문자열로 표현할 때 사용하는데 사실 str은 함수가 아니며, 기본 내장 클래스이다. 우리가 str(1)을 실행하는 것은 str 클래스의 생성자 메소드의 인자로 3을 주는것과 같다.
실제로 help(str)로 결과를 보면 아래와 같다.

Help on class str in module builtins:

class str(object)
...

이처럼 우리가 사용하는 많은 함수들이 클래스로 구현되어 있고 클래스에 구현되어 있는 메서드들을 통해 작동한다. 클래스안에 __str__ 과 같이 특별한 역할을 하는 메서드들이 있고 이 메서드를 special method 또는 magic method라고 한다.

이 메서드들은 잘 사용한다면, 인스턴스를 호출하거나 인스턴스끼리의 덧셈, 인덱싱 등 다양하게 활용할 수 있다. 따라서 여러 메서드들을 이 포스트에 정리해본다.

__call__

기본적으로 클래스로 생성된 인스턴스는 호출이 가능하지 않다.


class test:
    def __init__(self):
        self.a = 1
        
a = test()
callabel(a)

False

class test:
    def __init__(self):
        self.a = 1
    
    def __call__(self):
        print('hi')
        
a = test()
a()
callabel(a)

hi
True

__str__

객체를 생성해서 print()를 사용하면 주소값이 반환되는 것을 볼 수 있다.

class test:
    def __init__(self):
        self.a = 1

a = test()
print(a)

>>> <__main__.test object at 0x7fcc...>

클래스 내에서 __str__을 구현한다면 객체를 str로 표현할 수 있다.

class test:
    def __init__(self):
        self.a = 1
        
    def __str__(self):
        return 'str method'
        
a = test()
print(a)
str(a)

>>> str method
>>> 'str method'

여기서 재밌는 것은 print()가 인자들을 str화하여 출력해주는 역할을 한다.
이 사실은 아래의 __repr__과 같이 이해하면 좋다.

__repr__

__str__과 마찬가지로 repr (representation) 문자열 표현을 반환하는 메서드이다.

str과 차이점은 의도된 사용처가 다르다는 것이다.
위에서 설명한 print()안에 어떤 타입이 들어오든, 몇 개의 인자가 들어오든 출력결과를 반환해준다.
str은 universal interface로 구현되어 있기 때문에 표현에 사용하기 보다 타입이 다른 것들과 호환에 목적을 두고 있다.


class test:

    def __str__(self):
        return 'str method'

    def __repr__(self):
        return 'repr method'
        

a = test()

print(a)
print(str(a))
print(repr(a))
a

>>> str method
>>> str method
>>> repr method
>>> repr method

만약 str이 정의되어 있지 않다면?

class test:

    def __repr__(self):
        return 'repr method'

a = test()
print(str(a))
print(a)
print(repr(a))
a

>>> repr method
>>> repr method
>>> repr method
>>> repr method

전부 repr이 대신 사용되고 있는 것을 볼 수 있다.

정리를 하면 repr은 객체를 그 자체로 표현할 수 있는 메서드이기 때문에
단순히 a객체를 나타내면 repr메서드로 불러지는 것을 볼 수 있다.
print()는 str화 하기 때문에 당연히 str 메서드가 작동한다.

__len__

len을 정의하지 않으면 len()을 적용할 때, object of type 'test' has no len()의 오류가 발생한다. 우리가 직접 len을 정의하면 객체에 len()을 적용할 수 있다.

class test:
    def __init__(self, data):
        self.data = data

    def __len__(self):
        return len(data)
        

data = [1,2,3,4]
a = test(data)
len(a)

>>> 4

__iter__, __next__

iter 메서드는 iterable한 객체의 iterator를 반환한다.
실제 iteration을 수행하는 것은 iterator이며 iterator는 next메서드를 이용하여 요소들을 가져온다.
간단한 예를 들어 보자.

dir(list)
'__init__',
'__init_subclass__',
'__iter__',
 ...

리스트 object에 iter가 구현되어 있다. 따라서 반복 가능한 객체라고 볼 수 있다.

A = [1,2,3].__iter__()

print(A.__next__())
print(A.__next__())
print(A.__next__())
print(A.__next__())
1
2
3

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)

iter메서드를 사용하여 iterator를 생성하고, 이 iterator로 next를 이용하여 요소를 꺼낼 수 있으며 더이상 꺼낼 요소가 없으면 StopIteration이 발생한다.

클래스를 iterable한 객체로 만들고 싶으면 iter메서드를 구현해야 한다.
이 iter메서드가 반환하는 iterator는 동일한 클래스 객체거나 별도로 작성된 객체가 될 수 있다.

어떠한 경우든 iterator가 되는 클래스는 next 메서드를 구현해야 한다.
실제로 for loop문은 iterable한 객체만 이용할 수 있으며 해당 객체의 iter함수를 통해 iterator를 반환받고 그 iterator의 next메서드를 통하여 요소를 출력한다.

이터레이터 만들기

class test:
    def __init__(self, lst):
        self.lst = lst
        self.c = 0


    def __iter__(self):
        return self

    def __next__(self):
        if self.c >= len(self.lst):
            raise StopIteration

        r = self.c
        self.c += 1
        return self.lst[r]

lst = [1,2,3,4]
a = test(lst)
iterator = lst.__iter__()
print(iterator.__next__())
print(iterator.__next__())
print(iterator.__next__())
print(iterator.__next__())
print(iterator.__next__())
1
2
3
4

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
lst = [1,2,3,4]
a = test(lst)
for i in a:
    print(i)
1
2
3
4

iter 메서드의 리턴값으로 self를 사용하는 것은 동일한 클래스 객체를 iterator로 사용하겠다는 의미이다.

yield

iterator의 특수한 한 형태인 generator에 대해서 알아보자.
제너레이터는 함수안에서 yield를 사용하여 데이터를 하나씩 반환하는 함수이다.
제너레이터는 이터레이터의 한 형태이기 때문에 dir()로 확인해 보면 __iter__, __next__가 존재하는 것을 확인할 수 있다.

class test:
    def __init__(self, data):
        self.data = data
        self.count = 0

    def gen(self):

        for i in self.data:
            yield i


a = test(data)
for i in a.gen():
    print(i)
1
2
3
4
class test:
    def __init__(self, data):
        self.data = data
        self.count = 0

    def gen(self):

        for i in self.data:
            yield i


a = test(data)
generator = a.gen()
print(type(generator))
print(generator.__next__())
print(generator.__next__())
print(generator.__next__())
print(generator.__next__())
print(generator.__next__())
<class 'generator'>
1
2
3
4

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)

활용

class test:
    def __init__(self, data):
        self.data = data
        self.count = 0

    def __iter__(self):
        for i in self.data:
            yield i


a = test(data)

for i in a:
    print(i)
1
2
3
4

for loop문에 객체를 이용하면 iter함수를 호출하게 되며 함수 내에 yield로 인해 제너레이터가 되며 요소 값을 반환하게 된다.

__getitem__

iterator를 만드는 방법은 iter, next를 구현하는 방법 말고 getitem을 구현하는 방법도 있다.
차이점은 getitem은 인덱스로 접근을 할 수 있는 이터레이터이다.

class test:
    def __init__(self, data):
        self.data = data
        self.count = 0

    def __getitem__(self, idx):
        return self.data[idx]


data = [1,2,3,4]
a = test(data)
print(a[0])
print(a[1])
print(a[2])

for i in a:
    print(i)
1
2
3

1
2
3
4

getitem은 인덱스를 인자로 받아서 인덱스를 활용하여 반환해준다.

getattr()

객체의 속성을 얻을 수 있는 함수이다.

getattr(object, attribute_name, default) 속성이 존재하지 않으면 default값을 출력한다.

class test:
    age = 19
    def __init__(self, name):
        
        self.count = 0
        self.name = name

    def say(self, what):
        return f'my name is {self.name}, {what}'

print(getattr(test, 'age'))

a = test('june')

print(getattr(a, 'count'))
print(getattr(a, 'say')('hello'))
19
0
my name is june, hello

hasattr
hasattr(object, attribute_name) object안에 attribue가 있는지 확인하여 불린 값을 반환한다.

class test:
    age = 19
    def __init__(self, name):
        
        self.count = 0
        self.name = name

    def say(self, what):
        return f'my name is {self.name}, {what}'

a=test('june')

print(hasattr(a, 'number'))
print(hasattr(a, 'age'))
print(hasattr(a, 'say'))
False
True
True

__setitem__

getitem은 인덱스를 통해 값을 반환하지만 반대로 인덱스를 통해 값을 할당하고 싶을 때 setitem을 구현해주면 된다.

class test:
    def __init__(self, data):
        self.data = data
        self.count = 0

    def __getitem__(self, idx):
        return self.data[idx]

    def __setitem__(self, idx, value):
        
        return self.data.__setitem__(idx, value)
        
    def __str__(self):
        return str(self.data)
        
data = [1,2,3,4]
a = test(data)
print(a[0])
a[0] = 10
print(a[0])
print(a)
1
10
[10, 2, 3, 4]

setattr

setattr(object, attribute_name, value) 객체의 속성을 원하는 값으로 설정할 수 있다.
만약 존재하지 않는 속성이름을 사용하면 생성과 할당을 하게 된다.

class test:
    age = 19
    def __init__(self, name):
        
        self.count = 0
        self.name = name

    def say(self, what):
        return f'my name is {self.name}, {what}'

a=test('june')

setattr(a, 'age', 29)
print(a.age)

setattr(a, 'phone', '000-000')
print(a.phone)
29
000-000

__delitem__

del을 사용해 인덱스로 요소를 제거하고 싶을 경우 delitem을 구현한다.

class test:
    def __init__(self, data):
        self.data = data
        self.count = 0

    def __getitem__(self, idx):
        return self.data[idx]

    def __setitem__(self, idx, value):
        
        return self.data.__setitem__(idx, value)

    def __str__(self):
        return str(self.data)

    def __delitem__(self, idx):
        return self.data.__delitem__(idx)



data = [1,2,3,4]
a = test(data)
print(a[0])
a[0] = 10
print(a[0])
print(a)
del a[0]
print(a)
1
10
[10, 2, 3, 4]
[2, 3, 4]

delattr

delattr(object, attribute_name object에 attribute_name과 같은 속성을 삭제한다.

class test:
    age = 19
    def __init__(self, name):
        
        self.count = 0
        self.name = name

    def say(self, what):
        return f'my name is {self.name}, {what}'

a=test('june')

print(hasattr(a, 'name'))
delattr(a, 'name')
print(hasattr(a, 'name'))
True
False

__dict__

dict는 객체의 속성 정보를 확인하기 위해서 사용 한다.

class test:
    def __init__(self, name, age):
        self.data = [1,2,3,4]
        self.name = name
        self.age = age

a = test('june', 19)
a.__dict__

{'data': [1, 2, 3, 4], 'name': 'june', 'age': 19}

응용

json파일을 dict의 update를 이용해서 한번에 속성값으로 사용할 수 있다.

class test:
    def __init__(self, name, age):
        self.data = [1,2,3,4]
        self.name = name
        self.age = age
        self.data_uapate()
    
    def data_uapate(self):
        lst = {'data2':[5,6,7], 'data3' : [8,9]}
        self.__dict__.update(lst)

a = test('june', 19)
a.__dict__
{'data': [1, 2, 3, 4],
 'name': 'june',
 'age': 19,
 'data2': [5, 6, 7],
 'data3': [8, 9]}

__file__

파이썬 코드를 보다보면 __file__을 사용하는 코드를 볼 수 있다.
이 file의 의미는 현재 작업하고 있는 파일을 나타내는데 경로까지 같이 출력하게 된다.

print(__file__)

Users/khs/python/test.py

이런 기능을 이용하여 os, Path와 같은 파일시스템 라이브러리를 이용하여 다양하게 사용할 수 있다.

import os

print(os.path.basename(__file__))
print(os.path.dirname(__file__))

test.py
Users/khs/python

profile
CS | ML | DL

0개의 댓글