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.
공식문서 설명은 늘 어려움.
쉽게 말해, 부모나 형제 클래스의 임시 객체를 반환하고, 반환된 객체를 이용해 슈퍼 클래스의 메소드를 사용할 수 있음.
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를 넣어줘야함
(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를 할 수 없다.
위 수식을 이용해 오슬로(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)
위 수식을 검증하기 위해서 수식의 일부 값을 확인해야할 수 있는데 수식의 일부를 복&붙으로 확인할 수 있다.
이 경우 코드를 읽는 사람들에게 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 내에 포함되어있는지를 체크하여 가독성을 높일 수 있음
파이썬에서 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 정보를 보기 좋게 출력하면 아래와 같음
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 함
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.
먼저, EXIF는 Exchangeable image file format의 약자로 digital camera로 찍힌 이미지에 대한 다양한 meta 정보를 저장하는 프로토콜이다. EXIF 는 실제 이미지와 함께 저장되는데 shutter speed, focal length, orientation, shooting time 정보들이 있다.
그중 내가 관심있는 orientation tag는 1~8까지의 값을 가진다.
이미지에서 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))
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의 경우 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__ 메소드에서 사용하기 때문이다.