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 함
프로그래밍 언어란 아이디어를 다른 개발에게 전달하는 것이고 여기에 클린 코드의 진정한 본질이 있다
⇒ 클린 코드를 정의하기 보다는 좋은 코드 나쁜 코드의 차이점을 확인하고 훌륭한 코드와 좋은 아키텍쳐를 식별하여 자신만의 정의를 하는 것이 좋음
클린코드의 중요성
민첩한 개발과 지속적인 배포가 가능
유지보수가 가능한 상태로 가독성이 높아야 기획자가 새롭게 기능을 요구할 때마다 리팩토링을 하고 기술 부채를 해결하느라 시간이 오래 걸리지 않음
기술부채 발생
잠재적인 문제로 언젠가는 돌발 변수가 될 수 있음
클린 코드에서 코드 포매팅의 역할
PEP (Python Enhancement Proposal)
PEP 8: Style Guide for Python Code 로 가장 잘 알려진 표준이며 띄어쓰기, 네이밍 컨벤션, 줄 길이 제한 등의 가이드라인을 제공
클린 코드를 위한 보조적인 역할, PEP8을 100% 준수한다고 하더라도 여전히 클린 코드의 요건을 충족하지 못할 수 있음
PEP8 특징
검색 효율성
PEP8: 변수에 값을 할당하는 경우와 함수의 키워드 파라미터에 값을 할당하는 경우를 구분
# core.py
#
#
#
def get_location(location: str = ""):
pass
#변수에 값을 할당할 때 띄어쓰기 사용 O
current_location = get_location()
#키워드 인자에 값을 할당할 때 띄어쓰기사용 X
get_location(location=current_location)
def data_from_response(response: dict) -> dict:
"""response의 HTTP status가 200이라면 response의 payload를 반환
- response의 예제::
{
"status": 200, # <int>
"timestamp": "....", # 현재 시간의 ISO 포맷 문자열
"payload": {...} # 반환하려는 dictionary 데이터
}
"""
if response["status"] != 200:
raise ValueError
return {"data": response["payload"]}
input, output의 예상 형태를 더 잘 이해할 수 있고 단위 테스트에서도 유용한 정보로 사용됨
도구설정
반복적인 확인 작업을 줄이기 위해 코드 검사를 자동으로 실행하는 기본도구설정
데이터 타입 일관성 검사
mypy, pytype 등의 도구를 CI build에 포함시킬 수 있음
$ pip install mypy
from typing import Iterable
import logging
logger = logging.getLogger()
def broadcast_notification(message: str, relevant_user_emails: Iterable[str]):
for email in relevant_user_emails:
logger.warning(f"{message} 메세지를 {email}에게 전달")
broadcast_notification("welcome", "user1@domain.com")
# mypy가 오류 내뱉지 않음
$ mypy core.py
Success: no issues found in 1 source file
welcome 메세지를 u에게 전달 welcome 메세지를 s에게 전달 welcome 메세지를 e에게 전달 welcome 메세지를 r에게 전달 welcome 메세지를 1에게 전달 welcome 메세지를 @에게 전달 welcome 메세지를 d에게 전달 welcome 메세지를 o에게 전달 welcome 메세지를 m에게 전달 welcome 메세지를 a에게 전달 welcome 메세지를 i에게 전달 welcome 메세지를 n에게 전달 welcome 메세지를 .에게 전달 welcome 메세지를 c에게 전달 welcome 메세지를 o에게 전달 welcome 메세지를 m에게 전달
잘못된 호출. 문자열 또한 iterable 객체이므로 for 문이 정상 동작하지만 유효한 이메일 형식이 아님
리스트나 튜플만 허용하도록 더 강력한 타입 제한을 주면,
from typing import List, Tuple, Union
import logging
logger = logging.getLogger()
def broadcast_notification(
message: str, relevant_user_emails: Union[List[str], Tuple[str]]
):
for email in relevant_user_emails:
logger.warning(f"{message} 메세지를 {email}에게 전달")
broadcast_notification("welcome", "user1@domain.com")
> mypy core.py
core.py:14: error: Argument 2 to "broadcast_notification" has incompatible type "str"; expected "list[str] | tuple[str]" [arg-type]
Found 1 error in 1 file (checked 1 source file)
# 크롬 웹브라우저 다운로드 및 설치 (working directory: /root/crawling)
$ wget https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/120.0.6099.109/linux64/chrome-linux64.zip
$ unzip chrome-linux64.zip
2) 크롬 드라이버 설치
위 버전에 맞춰서 크롬 드라이버도 설치
# 크롬 드라이버 다운로드 및 설치 (working directory: /root/crawling)
$ wget https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/120.0.6099.109/linux64/chromedriver-linux64.zip
$ unzip chromedriver-linux64.zip
3) Python selenium 패키지 설치
pip install "selenium == 4.15.1"
크롤링하는 코드는 본 유투브 링크를 참고했고, apple이라는 키워드로 검색했을 때 나오는 이미지들을 저장하는 코드이다.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys # 엔터처리용
import time
import urllib.request
import os
options = webdriver.ChromeOptions()
options.add_argument("--headless") # '창이 없는’: 서버에서는 웹브라우저 창을 못띄우니 필요
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage") # 공유메모리 사용하지 않음
options.add_argument("--single-process")
options.binary_location = '/root/crawling/chrome-linux64/chrome'
service = webdriver.ChromeService(executable_path='/root/crawling/chromedriver-linux64/chromedriver')
driver = webdriver.Chrome(service=service, options=options)
URL = "<https://www.google.co.kr/imghp>"
KEYWORD = "apple"
driver.get(url=URL)
# time.sleep은 fixed, implicity는 flexible로 time_to_wait이 maximum time
driver.implicitly_wait(time_to_wait=10)
keyElement = driver.find_element(By.NAME, "q")
keyElement.send_keys(KEYWORD)
keyElement.send_keys(Keys.RETURN) # 키보드 엔터
bodyElement = driver.find_element(By.TAG_NAME, "body")
time.sleep(5) # 엔터치고 이미지 나오는 시간 기다림
image_candidates = []
print("Crawling Images Start!")
for i in range(1):
bodyElement.send_keys(Keys.PAGE_DOWN)
time.sleep(0.2)
images = driver.find_elements(
By.XPATH, '//*[@id="islrg"]/div[1]/div/a[1]'
) # XPATH는 변경될 수 있고 web browser의 개발자도구 이용해서 확인필요
image_candidates.append(images)
for images in image_candidates:
for idx, image in enumerate(images):
image.send_keys(Keys.ENTER)
time.sleep(0.5)
high_images = driver.find_elements(
By.XPATH,
'//*[@id="Sva75c"]/div[2]/div[2]/div[2]/div[2]/c-wiz/div/div/div/div/div[3]/div[1]/a/img[1]', # XPATH는 변경될 수 있고 web browser의 개발자도구 이용해서 확인필요
)
try:
real_image = high_images[0].get_attribute("src")
except Exception as e:
print(f"Exception: {e}")
continue
try:
urllib.request.urlretrieve(
real_image,
os.path.join(os.getcwd(), str(idx)) + ".jpg",
)
except Exception as e:
print(e)
정상적으로 동작하는걸 확인했고 Docker 이미지로 만들어서 쿠버네티스 파드로 띄우려고 했는데 아래와 같은 에러가 발생했다.
OSError: [Errno 8] Exec format error: '~~/chromedriver-linux64/chromedriver’
에러메세지가 분명하진 않다.
이것저것 시도 후 파악한 결과는 CPU 아키텍처에 맞는 chrome 웹브라우저와 드라이버를 다운받아야했다. 알아보니 쿠버네티스 pod는 AWS graviton 기반의 인스턴스에 뜨게 되는데 이는 arm64기반의 cpu였다!
결국 arm64 기반의 크롬 웹브라우저와 드라이버를 다운로드 후 해결했다.
ARM은 intel이나 amd와 호환되지 않는 cpu 아키텍쳐로 저전력 고효율을 목적으로 하는 곳(ex) 스마트폰)에 많이 사용되었는데 많이 발전해서 이제 PC용으로도 사용하는 시도가 많아지고 있다고 한다. 앞서 이야기한 AWS의 Graviton이나 Apple의 M1, M2칩이 그 예이다. Cloud환경에서 작업할 때 CPU 아키텍쳐도 고려해야한다는 걸 알 수 있는 경험이었다.
“A man, a plan, a canal: Panama” → 영문자만 추리면 아래와 같음
팰린드롬 맞음
문제에 대한 예시
풀이 1. 리스트로 변환
def is_palindrome_1(s:str)->bool:
strs = []
# 숫자, 문자만 추출
for char in s:
if char.isalnum():
strs.append(char.lower()) # 소문자로 변환
while len(strs)>1:
if strs.pop(0) != strs.pop():
return False
return True
문제풀이에 필요한 개념
String
isalnum() 메소드
Return True if the string is an alpha-numeric string, False otherwise.
맨 앞의 element에 pop()을 적용하면 뒤의 요소들을 한칸씩 왼쪽으로 모두 옮겨야 함
반면 deque의 popleft() 시간 복잡도는 O(1)
내부적으로 doubly linked list으로 삽입/삭제에 효율이 좋음
1→ 2→ 3→4→5→6, 2만 pop하면 1→3→4→5→6 (뒤는 유지)
풀이 3. 슬라이싱 사용
def is_palindrome_3(s:str) -> bool:
import re
s = s.lower()
s = re.sub('[^a-z0-9]', '', s) # 숫자 문자가 아닌 문자열을 공백으로 변환
return s==s[::-1]
문제 풀이에 필요한 개념
정규식
- [] : 문자 클래스로 [] 사이의 문자들과 매치
- [a-z] : 소문자 알파벳과 매치
- [a-zA-Z] : 알파벳 모두
- [0-9] : 숫자 모두
- [^a-z] : 소문자 알파벳을 제외한 문자와 매치
- [^a-z0-9] : 소문자 알파벳과 숫자를 제외한 문자와 매치
Singleton 클래스 인스턴스를 2개 생성했지만 각각의 주소가 같으므로 둘은 같은 인스턴스를 가리키고 있음
게으른 초기화
싱글톤 패턴을 기반으로 하는 초기화 방식
인스턴스를 꼭 필요할 떄 생성
모듈을 import 할 때 필요하지 않는 시점에 실수로 미리 객체를 생성하는 경우 방지
class Singleton:
__instance = None
def __init__(self):
if not Singleton.__instance:
print('__init__ method called')
else:
print("Instance already created:", self.get_instance())
@classmethod
def get_instance(cls):
if not cls.__instance:
cls.__instance = Singleton()
return cls.__instance
if __name__=='__main__':
s = Singleton() # __init__ method called 초기화만하고 객체 생성 X
print('object created', Singleton.get_instance()) # 객체 생성
# object created <__main__.Singleton object at 0x00000183220C51D0>
s1 = Singleton()
# Instance already created: <__main__.Singleton object at 0x00000246DBAB51D0>
# module1.py
import singleton
print('singleton in module_1: ', singleton.shared_variable)
singleton.shared_variable += "(modified by module_1)"
print('shared_variable was modified in module_1)
# module2.py
import singleton
print('singleton in module2: ', singleton.shared_variable)
위 3개의 .py 파일을 만든 후 모두 import
# test.py
import module1
# singleton in module1: shared variable
import module2
# singleton in module2: shared variable(modified by module_1)
singleton 모듈을 module1.py, module2.py 에서 각각 import
module1.py 에서 수정된 singleton 모듈의 변수(shared_variable)가 module2.py에서도 수정된 채로 확인됨
본 글에서는 Linked List에서 노드 삭제에 대해 다룸. cur이라는 변수를 이용하여 head가 가리키는 노드부터 삭제할 노드를 찾음.
노드 삭제에 대해서 크게 3가지 경우로 나눠서 생각
1) Linked list가 비어있는 경우 : 메소드 바로 종료
cur = self.head
if cur is None:
return
2) head가 가리키는 노드의 값이 삭제하고자하는 값인 경우 (아래 그림에서 data 변수가 3인 경우)
cur = self.head
head는 현재 가리키는 노드의 next가 가리키는 노드를 참조해야 함. 그리고 cur에는 None 할당
if cur.data == data: # 첫 노드 삭제
self.head = cur.next
cur = None
return
3) head가 가리키는 노드가 아닌 다른 노드를 삭제하는 경우 (data에 2가 담긴 노드를 삭제한다고 가정)
cur = self.head
prev 변수를 도입하여 cur이 가리키는 노드를 참조하고, cur 변수는 현재 가리키는 노드의 next가 가리키는 노드 참조.
prev = cur
cur = cur.next
그리고 cur 변수가 가리키는 노드의 data가 삭제할 데이터인지 확인하는 과정을 반복하고 맞으면 반복과정을 종료
while True:
prev = cur
cur = cur.next
if cur.data == data:
break
Linked List가 끊기지 않기 위해서는 prev가 가리키는 노드의 next가 cur노드가 가리키는 노드의 next가 가리키는 노드를 참조해야 함 (아래 그림에서 빨간선)
prev.next = cur.next
위 3가지 경우가 포함된 delete 메소드는 아래와 같음
def delete(self, data): # data가 포함된 노드를 linked list에서 삭제
cur = self.head
if cur == None: # Linked List가 비어있는 경우
return
if cur.data == data: # 첫 노드 삭제
self.head = cur.next
cur = None
return
while True:
prev = cur
cur = cur.next
if cur.data == data: # 중간 노드 삭제
break
prev.next = cur.next
cur = None
전체 코드
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
self.tail = None
# linked list 끝에 노드 추가
def append(self, new_data):
new_node = Node(new_data)
if self.head is None:
self.head = new_node
else:
self.tail.next = new_node
self.tail = new_node
# linked list 처음에 노드 추가
def push(self, new_data):
new_node = Node(new_data)
if self.head is None:
self.tail = new_node
else:
new_node.next = self.head
self.head = new_node
def delete_all(self):
while self.head is not None:
cur = self.head
self.head = self.head.next
cur = None
# data가 포함된 노드를 linked list에서 삭제
def delete(self, data):
cur = self.head
if cur == None: # Linked List가 비어있는 경우
return
if cur.data == data: # 첫 노드 삭제
self.head = cur.next
cur = None
return
while True:
prev = cur
cur = cur.next
if cur.data == data: # 중간 노드 삭제
break
prev.next = cur.next
cur = None
def print(self):
cur = self.head
while cur:
print(cur.data, end=" ")
cur = cur.next
print()
if __name__ == "__main__":
linked_list = LinkedList()
linked_list.print() # 출력 없음
linked_list.push(1)
linked_list.print() # 1
linked_list.push(2)
linked_list.print() # 2 1
linked_list.push(3)
linked_list.print() # 3 2 1
linked_list.delete(2)
linked_list.print() # 3 1