반응형

Python 공식문서에 따르면 super 클래스의 역할은 아래와 같음

Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.

공식문서 설명은 늘 어려움.

쉽게 말해, 부모나 형제 클래스의 임시 객체를 반환하고, 반환된 객체를 이용해 슈퍼 클래스의 메소드를 사용할 수 있음.

즉, super() 를 통해 super class의 메소드에 접근 가능

단일상속에서 super()

 
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

 

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

square = Square(4)
square.area() # 16
  • Rectangle 클래스를 상속받기 때문에 Rectangle의 area() 메소드 사용 가능

 

super() with parameters


  • super() 는 2가지 파라미터를 가질 수 있음
    • 첫번째 : subclass
    • 두번째 : subclass의 인스턴스 객체
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

class Square(Rectangle):
    def __init__(self, length):
        super(Square, self).__init__(length, length)
  • 단일 상속인 경우에는 super(Square, self)와 super()는 같은 의미

아래의 경우는?

class Cube(Square):
    def surface_area(self):
        face_area = super(Square, self).area()
        return face_area * 6

super(Square, self).area()

첫번째 argument : subclass 인 Square

  • Cube가 아닌 Square기 때문에 super(Square, self)의 반환은 Square 클래스의 부모 클래스인 Rectangle 클래스의 임시 객체
  • 결과적으로 Rectangle 인스턴스에서 area() 메소드를 찾음

Q. Square 클래스에 area 메소드를 구현하면??

  • 그래도 super(Square, self) 가 Rectangle 클래스를 반환하기 때문에 Rectangle 인스턴스에서 area() 메소드를 호출
## super 클래스의 정의
class super(object):
	def __init__(self, type1=None, type2=None): # known special case of super.__init__
	        """
	        super() -> same as super(__class__, <first argument>)
	        super(type) -> unbound super object
	        **super(type, obj) -> bound super object; requires isinstance(obj, type)
	        super(type, type2) -> bound super object; requires issubclass(type2, type)**
	        Typical use to call a cooperative superclass method:
	        class C(B):
	            def meth(self, arg):
	                super().meth(arg)
	        This works for class methods too:
	        class C(B):
	            @classmethod
	            def cmeth(cls, arg):
	                super().cmeth(arg)
					"""
	        
	        # (copied from class doc)

두번째 argument : 첫번째 argument의 클래스 인스턴스를 넣어주거나 subclass를 넣어줘야함

print(issubclass(Cube, Square)) # True
반응형
반응형

정식 이름은 Assignment expression operator인데 walrus operator라고도 불린다.

walrus는 “바다코끼리”라는 뜻으로 operator가 바다 코끼리의 눈과 이빨을 닮아서 이렇게 부른다.
때론 colon(:) equals(=) operator라고도 한다.

Python 3.8버전부터 새로 등장했다.

 

https://dev.to/davidarmendariz/python-walrus-operator-j13

 

Statement vs Expression in Python

바다코끼리 연산자의 정식 이름을 보면 Assignment expression operator로, expression이라는 단어가 나온다. 

Python에서 statement와 expression이라는 표현이 비슷해 혼동스러운데 간단히 정리하면, 아래와 같다.

  • statement: 코드를 구성할 수 있는 단위 혹은 모든 것
  • expression: 값을 평가하는 statement로 연산자와 피연산자의 조합으로 구성됨

예시

x = 25          # a statement
x = x + 10      # an expression
  • statement는 변수를 생성하는데 사용된다.
  • expression은 x값에 10을 더하는 연산이 수행된 후 결과가 x에 할당되었다.
>>> walrus = False # (1)
>>> walrus
False

>>> (walrus := True) # (2)
True
>>> walrus
True
  1. walrus = False는 값 False가 walrus에 할당된다. (traditional statement)
  2. (walrus := True) 는 assignment expression으로 walrus에 값 True를 할당한다.

둘의 미묘한 차이중 하나는 walrus = False는 값을 반환하지 않지만 (walrus := True)는 값을 반환한다는 것이다!

>>> walrus = False
>>> (walrus := True)
True

 

등장한 이유

PEP 572에 Abstract에 아래와 expression 내에서 변수에 할당하는 방법을 제안하고 있다. 

creating a way to assign to variables within an expression using the notation NAME := expr.

C언어에서는 변수에 값을 할당하는 statement도 expression인데 강력하지만 찾기 힘든 버그를 생산하기도 한다.

int main(){
	int x = 3, y = 8;
	if (x = y) {
	    printf("x and y are equal (x = %d, y = %d)", x, y);
	}
	return 0;
}

x와 y값을 비교후 값이 같으면 두 값을 출력하는 코드지만 x와 y값이 다르기 때문에 아무것도 출력 안되길 기대되지만 실제 코드 실행 결과는 아래와 같이 print 문이 출력된다. 왜일까?

x and y are equal (x = 8, y = 8)

문제는 위 코드 세번째 줄 if (x = y) 에서 equality comparison operator(==) 대신 assignment operator(=) 를 사용하고 있기 때문이다. if 문의 조건에는 expression이 와야하는데 C언어에서는 x = y를 expression으로 x값이 8로 할당되고 1이상의 값으로 True로 판단되서 print문이 출력된다.

그럼 Python에서는? 

x, y = 3, 8
if x = y:
    print(f"x and y are equal ({x = }, {y = })")
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
  • Syntax Error를 내뱉는데 expression이 아닌 statement이기 때문이다. 파이썬은 이를 분명히 구분하고 walrus operator에도 이러한 설계 원칙이 반영되었다. 그래서 walrus operator를 이용해서 일반적인 assignment를 할 수 없다.
>>> walrus := True
  File "<stdin>", line 1
    walrus := True
           ^
SyntaxError: invalid syntax

이를 해결하기 위해 많은 경우에 assignment expression 에 괄호를 추가해 python에서 syntax error를 피할 수 있다.

>>> (walrus := True)  # Valid, but regular assignments are preferred
True

 

사용 예시

walrus operator는 반복적으로 사용되는 코드를 간단히 하는데 유용하게 사용될 수 있다.

(1) 수식 검증

예로 복잡한 수식을 코드로 작성하고 이름 검증하고 debugging할 때 walrus operator가 유용할 수 있다.

아래와 같은 수식이 있다고 하자 (참고: haversine formula, 지구 표면의 2점 사이의 거리를 구하는 식)

$$
2 \cdot \text{r} \cdot \arcsin\left(
    \sqrt{
        \sin^2\left(\frac{\phi_2 - \phi_1}{2}\right)
        + \cos(\phi_1) \cdot \cos(\phi_2) \cdot \sin^2\left(\frac{\lambda_2 - \lambda_1}{2}\right)
    }
\right)
$$ 

  • ϕ: 위도(latitude), λ: 경도(longitude)

위 수식을 이용해  오슬로(59.9°N 10.8°E) 와 밴쿠버(49.3°N 123.1°W)  사이의 거리를 구하면,

from math import asin, cos, radians, sin, sqrt
# Approximate radius of Earth in kilometers
rad = 6371
# Locations of Oslo and Vancouver
ϕ1, λ1 = radians(59.9), radians(10.8)
ϕ2, λ2 = radians(49.3), radians(-123.1)
# Distance between Oslo and Vancouver
print(2 * rad * asin(
    sqrt(
        sin((ϕ2 - ϕ1) / 2) ** 2
        + cos(ϕ1) * cos(ϕ2) * sin((λ2 - λ1) / 2) ** 2
    )
))

# 7181.7841229421165 (km)
  • 위 수식을 검증하기 위해서 수식의 일부 값을 확인해야할 수 있는데 수식의 일부를 복&붙으로 확인할 수 있다.
  • 이때 walrus operator를 이용하면,
2 * rad * asin(
    sqrt(
        **(ϕ_hav := sin((ϕ2 - ϕ1) / 2) ** 2)**
        + cos(ϕ1) * cos(ϕ2) * sin((λ2 - λ1) / 2) ** 2
    )
)

# 7181.7841229421165

ϕ_hav
# 0.008532325425222883
  • 전체 expression의 값을 계산하면서 동시에 ϕ_hav값을 계속 확인할 수 있어서 copy & paste로 인해 발생할 수 있는 오류의 가능성을 줄일 수 있다.

 

(2) Lists 에서 활용될 수 있는 walrus operator

numbers = [2, 8, 0, 1, 1, 9, 7, 7]

위 list에서 길이, 합계, 평균 값을 dictionary에 저장한다고 가정해보자

description = {
    "length": len(numbers),
    "sum": sum(numbers),
    "mean": sum(numbers) / len(numbers),
}

print(description) # {'length': 8, 'sum': 35, 'mean': 4.375}
  • description에서 numbers의 len과 sum이 각각 두번씩 호출된다
  • 짧은 list에서는 큰 문제가 되지 않지만 길이가 더 긴 list나 연산이 복잡할 경우에는 최적화할 필요가 있다

 

물론 아래처럼 len_numbers, sum_numbers 변수를 dictionary 밖에서 선언 후 사용할 수도 있다

numbers = [2, 8, 0, 1, 1, 9, 7, 7]

len_numbers = len(numbers)
sum_numbers = sum(numbers)

description = {
    "length": len_numbers,
    "sum": sum_numbers,
    "mean": sum_numbers / len_numbers,
}

print(description) # {'length': 8, 'sum': 35, 'mean': 4.375}

 

 

하지만 walrus operator를 이용해 len_numbers, sum_numbers 변수를 dictionary 내부에서만 사용하여 code를 최적화할 수 있다

numbers = [2, 8, 0, 1, 1, 9, 7, 7]

description = {
    "length": (len_numbers := len(numbers)),
    "sum": (sum_numbers := sum(numbers)),
    "mean": sum_numbers / len_numbers,
}

print(description) # {'length': 8, 'sum': 35, 'mean': 4.375}
  • 이 경우 코드를 읽는 사람들에게 len_numbers와 sum_numbers 변수는 계산을 최적화하기 위해 dictionary 내부에서만 사용했고 다시 사용되지 않음을 명확히 전달 할 수 있다

 

(3) Text 파일에서 lines, words, character 수 세는 예시

# wc.py
import pathlib
import sys

for filename in sys.argv[1:]:
    path = pathlib.Path(filename)
    counts = (
        path.read_text().count("\\n"),  # Number of lines
        len(path.read_text().split()),  # Number of words
        len(path.read_text()),  # Number of characters
    )
    print(*counts, path) # 11 32 307 wc.py
  • wc.py 파일은 11줄, 32단어, 307 character로 구성되어있다
  • 위 코드를 보면 path.read_text() 가 반복적으로 호출되는걸 알 수 있다 ⇒ walrus operator를 이용해 개선해보면,
import pathlib
import sys

for filename in sys.argv[1:]:
    path = pathlib.Path(filename)
    counts = (
        **(text := path.read_text()).count("\\n"),  # Number of lines**
        len(text.split()),  # Number of words
        len(text),  # Number of characters
    )
    print(*counts, path)

 

물론 아래처럼 text 변수를 이용하면 코드는 한줄 늘어나지만 readability를 훨신 높일 수 있다.

import pathlib
import sys

for filename in sys.argv[1:]:
    path = pathlib.Path(filename)
    text = path.read_text()
    counts = (
        text.count("\\n"),  # Number of lines
        len(text.split()),  # Number of words
        len(text),  # Number of characters
    )
    print(*counts, path)

그러므로 walrus operator가 코드를 간결하게 해주더라도 readability를 고려해야 한다.

 

(4) List Comprehensions

  • List comprehension과 함께 연산이 많은 함수를 사용하게 될 때, walrus operator의 사용은 효과적일 수 있다.
import time

t_start = time.time()

def slow(num):
    time.sleep(5)
    return num

numbers = [4, 3, 1, 2, 5]

results = [slow(num) for num in numbers if slow(num) > 4]

t_end = time.time()

print("elapsed time: ", t_end - t_start)

elapsed time: 30.01522707939148

  • numbers 리스트의 각 element에 slow 함수를 적용 후 3보다 큰 경우에만 results에 slow 호출 결과를 저장하는 코드
  • 문제는 slow 함수가 2번 호출됨
    • slow 호출 후 반환 결과가 3보다 큰지 확인할 때
    • results 리스트에 저장하기 위해 slow 호출할 때

가장 일반적인 해결책은 list comprehension 대신 for loop을 사용하는 것이다.

import time

t_start = time.time()

def slow(num):
    time.sleep(5)
    return num

numbers = [4, 3, 1, 2, 5]

results = []
for num in numbers:
    slow_num = slow(num)
    if slow_num > 4:
        results.append(slow_num)

t_end = time.time()

print("elapsed time: ", t_end - t_start)

elapsed time: 25.021725063323975

  • slow 함수가 모든 경우에 한번씩만 호출됨
  • 하지만 코드 양이 늘어나고 가독성이 떨어짐

walrus operator를 사용하면 list comprehension을 유지하면서 가독성을 높일 수 있음

import time

t_start = time.time()

def slow(num):
    time.sleep(5)
    return num

numbers = [4, 3, 1, 2, 5]

results = [slow_num for num in numbers if (slow_num := slow(num)) > 4]
print(results)

t_end = time.time()

print("elapsed time: ", t_end - t_start)

elapsed time: 25.018176908493042

 

(5) While Loop

question = "Do you use the walrus operator?"
valid_answers = {"yes", "Yes", "y", "Y", "no", "No", "n", "N"}

user_answer = input(f"\n{question} ")
while user_answer not in valid_answers:
    print(f"Please answer one of {', '.join(valid_answers)}")
    user_answer = input(f"\n{question} ")
  • 위 코드는 사용자의 입력을 받는 input 함수가 두번 반복됨
  • 이를 개선하기 위해 While True 와 break를 사용하여 코드를 다시 작성하는 것이 일반적임
question = "Do you use the walrus operator?"
valid_answers = {"yes", "Yes", "y", "Y", "no", "No", "n", "N"}

while True:
    user_answer = input(f"\n{question} ")
    if user_answer in valid_answers:
        break
    print(f"Please answer one of {', '.join(valid_answers)}")

 

walrus operator를 이용해서 while loop을 간결하게 할 수 있음

question = "Do you use the walrus operator?"
valid_answers = {"yes", "Yes", "y", "Y", "no", "No", "n", "N"}

while (user_answer := input(f"\n{question} ")) not in valid_answers:
    print(f"Please answer one of {', '.join(valid_answers)}")
  • 사용자로부터 받은 input 입력을 user_answer 변수에 저장하고 동시에 valid_answers 내에 포함되어있는지를 체크하여 가독성을 높일 수 있음

 

Reference


https://realpython.com/python-walrus-operator/

 
반응형
반응형

 

 

파이썬에서 URL을 다루기 위한 패키지로 크게 3가지 종류가 있음; urllib, urllib3, requests

  • urllib은 built-in package이고 나머지 2개는 third party

 

사용방법

 

1. 기본 사용방법

from urllib.request import urlopen                     # (1)

with urlopen("<https://www.example.com>") as response: # (2)
    body = response.read()                             # (3)
    print(type(body))                                  # (4)

(1) urllib.request는 built-in package로 따로 설치하지 않아도 됨. HTTP request를 위해 urlopen을 사용

(2) context manager with 문을 통해 request 후 response를 받을 수 있음

(3) response 는 <http.client.HTTPResponse> 객체

  • read 함수를 통해 bytes로 변환할 수 있음

(4) 실제 body의 type을 print해서 bytes 타입임을 확인

 

2. GET request for json format response

  • API 작업시 response가 json format인 경우가 많음
from urllib.request import urlopen
import json                                            # (1)

url = "<https://jsonplaceholder.typicode.com/todos/1>" # (2)
with urlopen(url) as response:
    body = response.read()

print("body: ", body)                                  # (3)
# body:  b'{\\n  "userId": 1,\\n  "id": 1,\\n  "title": "delectus aut autem",\\n  "completed": false\\n}'

# json bytes to dictionary
todo_item = json.loads(body)                           # (4)
print(todo_item)
# {'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}

(1) urllib 패키지와 함께 json 포맷을 다루기 위해 json package 추가

(2) JSON 형태의 데이터를 얻기 위한 샘플 API 주소

(3) 응답을 print 해보면 json 형태의 bytes format. 이를 dictionary 형태로 변경해주기 위해 json 패키지 필요

(4) json bytes를 파이썬 객체인 dictionary로 변경하기 위해 json.loads 함수 사용

 

3. Response의 header 정보 얻는 방법

from urllib.request import urlopen
from pprint import pprint

with urlopen("<https://www.example.com>") as response:
    pprint(response.headers.items())                       # (1)
    pprint(response.getheader("Connection")) # 'close'     # (2)
  • response의 headers.items()를 통해 header 정보를 얻을 수 있음

(1) pretty print(pprint)를 이용해 header 정보를 보기 좋게 출력하면 아래와 같음

[('Accept-Ranges', 'bytes'), ('Age', '78180'), ('Cache-Control', 'max-age=604800'), ('Content-Type', 'text/html; charset=UTF-8'), ('Date', 'Sat, 24 Aug 2024 18:10:20 GMT'), ('Etag', '"3147526947"'), ('Expires', 'Sat, 31 Aug 2024 18:10:20 GMT'), ('Last-Modified', 'Thu, 17 Oct 2019 07:18:26 GMT'), ('Server', 'ECAcc (lac/5598)'), ('Vary', 'Accept-Encoding'), ('X-Cache', 'HIT'), ('Content-Length', '1256'), ('Connection', 'close')]

(2) header의 개별 정보는 getheader 메소드를 이용해 얻을 수 있음

 

4. bytes를 string으로 변환

from urllib.request import urlopen

with urlopen("<https://www.example.com>") as response:
    body = response.read()                            
    print(type(body)) # <class 'bytes'>                       # (1)

decoded_body = body.decode("utf-8")                           # (2)
print(type(decoded_body)) # <class 'str'>                     # (3)
print(decoded_body[:30])

(1) body의 type을 확인해보면 bytes 이고 아래와 같은 형태이다

b'<!doctype html>\n<html>\n<head>\n

(2) bytes를 string으로 변환하기 위해 decode method를 이용 (”utf-8”을 파라미터로 전달)

(3) decoded_body의 type을 확인해보면 string인걸 확인할 수 있고 decoded_body의 일부를 표시하면 아래와 같은 형태

<!doctype html>
<html>
<head>

 

5. Bytes를 file로 변환

크게 2가지 방법이 있음

encoding & decoding 없이 바로 file로 작성

from urllib.request import urlopen

with urlopen("<https://www.example.com>") as response:
    body = response.read()

with open("example.html", mode="wb") as html_file:
    html_file.write(body)
  • write binary(wb) mode로 파일을 열어 bytes를 바로 example.html 파일에 작성
  • 코드를 실행하면 example.html 파일이 생성됨

 

contents를 file로 encoding해야하는 경우

from urllib.request import urlopen

with urlopen("<https://www.google.com>") as response:
    body = response.read()

character_set = response.headers.get_content_charset()        # (1) 
content = body.decode(character_set)                          # (2)

with open("google.html", encoding="utf-8", mode="w") as file: # (3)
    file.write(content)

(1)&(2): 구글같은 홈페이지는 location에 따라 다른 encoding 방식을 사용하기도 한다. 그래서 get_content_charset () 메소드를 이용해서 encoding 방식을 확인 후 bytes를 string으로 decoding 함

(3) decoded string을 다시 html에 utf-8 모드로 encoding해서 google.html 파일에 저장함

References


https://realpython.com/lessons/python-urllib-request-overview/

 
반응형
반응형

현상

 

1) 핸드폰을 세로로 길게 세운 상태에서 사진을 찍었다 (iPhone 12, iOS: 16.6.1)

전기구이통닭 시켜서 서비스로 받은 콜라

2) Python에서 pillow 이용해서 이미지를 열었는데 자동으로 반시계방향으로 90도 회전이 되어 읽어진다

from PIL import Image, ImageFile

ImageFile.LOAD_TRUNCATED_IMAGES = True # OSError: image file is truncated 에러때문에 추가했다

image_path = 'service_coke.jpeg'

img = Image.open(image_path)
img.show(title='test')

 

왜 자동으로 이미지를 돌릴까?

해결책은 github pillow repo의 issue에서 찾을 수 있었다. https://github.com/python-pillow/Pillow/issues/4703


I would imagine that the images you are dealing with have an EXIF orientation tag. This means that the image data is saved in one position, and then the image instructs the viewer to rotate it another way.

Pillow does not automatically apply this transformation to the data. However, it has a method to do so - https://pillow.readthedocs.io/en/stable/reference/ImageOps.html#PIL.ImageOps.exif_transpose

Here is an example using it. You can try this and check if it resolves your problem.

from PIL import Image, ImageOps
im = Image.open("file.jpg")
im = ImageOps.exif_transpose(im)

 

최종 코드

from PIL import Image, ImageFile, ImageOps

ImageFile.LOAD_TRUNCATED_IMAGES = True

image_path = 'service_coke.jpeg'

img = Image.open(image_path)
img = ImageOps.exif_transpose(img)
img.show(title='test')

짜잔

 

EXIF orientation tag는 뭘까?

먼저, EXIF는 Exchangeable image file format의 약자로 digital camera로 찍힌 이미지에 대한 다양한 meta 정보를 저장하는 프로토콜이다. EXIF 는 실제 이미지와 함께 저장되는데 shutter speed, focal length, orientation, shooting time 정보들이 있다. 

그중 내가 관심있는 orientation tag는 1~8까지의 값을 가진다. 

https://jdhao.github.io/2019/07/31/image_rotation_exif_info/

이미지에서 orientation tag를 얻는 방법

from PIL.ExifTags import TAGS
from PIL import Image

image_path = 'service_coke.jpeg'

img = Image.open(image_path)
exif = img.getexif()

for k, v in exif.items():
    print('{}: {}'.format(TAGS[k], v))

orientation tag:  6
TileWidth: 512
TileLength: 512
ResolutionUnit: 2
ExifOffset: 240
Make: Apple
Model: iPhone 12
Software: 16.6.1
Orientation: 6
DateTime: 2024:01:14 23:25:06
YCbCrPositioning: 1
XResolution: 72.0
YResolution: 72.0
HostComputer: iPhone 12

이런 정보를 확인할 수 있고 이 중 orientation tag 값은 6을 보여준다.

6은 위 이미지에서 볼 떄 image viewer에서 보여줄 때 이미지를 시계 방향으로 90도 회전해서 보여줘야된다는 의미이고 이를 반영하여 image를 보여주기 위해서는 위의 최종 코드에서 ImageOps.exif_transpose를 사용해야한다. 

from PIL import Image, ImageFile, ImageOps

image_path = 'service_coke.jpeg'
img = Image.open(image_path)
img = ImageOps.exif_transpose(img)
img.show(title='test')

 

exif_transpose 함수 정의

def exif_transpose(image, *, in_place=False):
    """
    If an image has an EXIF Orientation tag, other than 1, transpose the image
    accordingly, and remove the orientation data.

    :param image: The image to transpose.
    :param in_place: Boolean. Keyword-only argument.
        If ``True``, the original image is modified in-place, and ``None`` is returned.
        If ``False`` (default), a new :py:class:`~PIL.Image.Image` object is returned
        with the transposition applied. If there is no transposition, a copy of the
        image will be returned.
    """

- exif_transpose 함수는 이미지에 EXIF Orientation tag 정보가 있으면 이를 이용하여 image를 보여준다. 

 
반응형
반응형

공식문서에 따르면 Python에서는 함수나 변수 타입의 annotation을 강제하지 않는다. annotation이란 type hint를 생각하면 된다.

[c++]

int do_something(int x)
{
	return x + 1;
{

do_something이란 함수에 대해서 input parameter와 함수의 return 타입은 모두 int형으로 이를 어길시 에러가 발생한다.

[Python]

def do_something(x: int) -> int:
	return x + 1


print(do_something(1.1))  # 2.1

하지만 python의 경우 type hint를 어겨도 에러가 발생하지 않고, 위의 예와 같이 input parameter의 float 인 1.1을 넣어줘도 함수의 내용에 따라 2.1이 반환된다. 

 

하지만 annotation으로 에러가 발생하는 경우도 있는데 아래와 같이 forward reference와 연관된 경우이다. Forward reference란 메소드에서 변수를 먼저 사용하고, 그 이후에 변수를 정의하는 것이다. 

class A:
    def __init__(self, a: A):
        pass

# NameError: name 'A' is not defined

class A를 정의할 때, __init__ 메소드에서 parameter로 a를 받고 이는 class A라고 annotation이 표시되어있다. 하지만 위의 코드와 같이 작성하면 NameError가 발생하는데 그 이유는 class A의 정의가 끝나지 않는 상태에서 __init__ 메소드에서 사용하기 때문이다. 

위 문제를 해결하기 위해 크게 2가지가 있다.

1) type hint를 string으로 작성

class A:
    def do_something(self, a: 'A'):
        pass

아래의 코드를 통해서 해당 메소드의 annotation을 확인할 수 있다. 

print(A().do_something.__annotations__)  # {'a': 'A', 'return': None}

 

2) Python 3.7 이상 버전에서 from __future__ import annotations 추가

from __future__ import annotations

class A:
    def do_something(self, a: A) -> None:
        pass

class A의 정의가 끝나지 않는 상황에서 A를 annotation으로 사용하지만 에러가 발생하지 않는다.

아래의 코드를 통해 해당 메소드의 annotation들을 확인해보면 자동으로 string으로 변환된 것을 알 수 있다.

print(A().do_something.__annotations__) # {'a': 'A', 'return': None}

 

반응형

+ Recent posts