반응형

Python에서 JSON(JSON(JavaScript Object Notation))은 데이터를 구조적으로 표현하기 위한 포맷으로, 웹 애플리케이션에서 자주 사용됩니다. Python에서 JSON을 처리하는 방법은 json 모듈을 사용하여 쉽게 다룰 수 있습니다. 이 모듈을 통해 JSON 데이터를 파싱하거나 직렬화(serialize)할 수 있습니다.

 

다음은 Python을 활용한 JSON 처리 방법 3가지와 예시입니다:

1. JSON 문자열을 Python 객체로 변환 (JSON 파싱)

json.loads() 함수를 사용하여 JSON 형식의 문자열을 Python 객체로 변환할 수 있습니다. 이 함수는 JSON 문자열을 파싱하여 Python의 데이터 타입(딕셔너리, 리스트 등)으로 변환합니다.

import json

# JSON 형식의 문자열
json_string = '{"name": "Alice", "age": 30, "city": "New York"}'

# JSON 문자열을 Python 객체로 변환
data = json.loads(json_string)

# 변환된 데이터 출력
print(data)  # {'name': 'Alice', 'age': 30, 'city': 'New York'}
print(type(data))  # <class 'dict'>

 

2. Python 객체를 JSON 문자열로 변환 (직렬화)

json.dumps() 함수를 사용하여 Python 객체를 JSON 형식의 문자열로 변환할 수 있습니다. 이 함수는 Python 객체(예: 딕셔너리, 리스트 등)를 JSON 문자열로 직렬화합니다.

import json

# Python 객체 (딕셔너리)
data = {
    "name": "Bob",
    "age": 25,
    "city": "San Francisco"
}

# Python 객체를 JSON 문자열로 변환
json_string = json.dumps(data, indent=4)

# JSON 문자열 출력
print(json_string)
# 출력
{
    "name": "Bob",
    "age": 25,
    "city": "San Francisco"
}

indent 인자를 사용하여 출력 형식을 더 읽기 쉽게 할 수 있습니다.

 

3. JSON 파일 읽기 및 쓰기

Python에서 json.load()와 json.dump()를 사용하여 JSON 파일을 읽고 쓸 수 있습니다.

 

예시 1: JSON 파일 쓰기

import json

# Python 객체 (딕셔너리)
data = {
    "name": "Charlie",
    "age": 35,
    "city": "Los Angeles"
}

# JSON 파일에 데이터 쓰기
with open('data.json', 'w') as file:
    json.dump(data, file, indent=4)

 

예시 2: JSON 파일 읽기

import json

# JSON 파일에서 데이터 읽기
with open('data.json', 'r') as file:
    data = json.load(file)

# 읽은 데이터 출력
print(data)
# 출력
{'name': 'Charlie', 'age': 35, 'city': 'Los Angeles'}

 

요약

  1. json.loads() : JSON 문자열을 Python 객체로 변환.
  2. json.dumps() : Python 객체를 JSON 문자열로 변환.
  3. json.load() : JSON 파일을 읽어 Python 객체로 변환.
  4. json.dump() : Python 객체를 JSON 파일에 저장.

이렇게 json 모듈을 사용하면 JSON 데이터를 쉽고 효율적으로 처리할 수 있습니다.

반응형

'프로그래밍 > [ Python ]' 카테고리의 다른 글

[Python] 예외처리 try, except  (0) 2024.11.14
[Python] map  (0) 2024.11.13
[Python] List Comprehension  (0) 2024.11.12
[Python] Files (파일 관련)  (1) 2024.02.26
[python] pass, continue, break 활용법  (0) 2021.06.20
반응형

파이썬에서 예외 처리(Exception Handling)는 프로그램 실행 중 발생할 수 있는 오류를 처리하기 위한 기법입니다. 예외 처리 덕분에 오류가 발생해도 프로그램이 비정상적으로 종료되지 않고, 지정된 대체 작업을 수행하거나 오류 메시지를 사용자에게 적절히 제공할 수 있습니다.

기본적인 예외 처리 구문

파이썬에서 예외 처리는 try, except, else, finally 구문을 사용합니다.

  • try: 예외가 발생할 수 있는 코드를 작성합니다.
  • except: 예외가 발생했을 때 처리할 코드를 작성합니다.
  • else: 예외가 발생하지 않았을 때 실행할 코드를 작성합니다.
  • finally: 예외 발생 여부와 상관없이 항상 실행되는 코드를 작성합니다.

 

기본 구조

try:
    # 예외가 발생할 수 있는 코드
except ExceptionType as e:
    # 예외 처리 코드
else:
    # 예외가 발생하지 않았을 경우 실행되는 코드
finally:
    # 항상 실행되는 코드 (자원 정리 등)

 

 

예시 1: 0으로 나누기 예외 처리

try:
    x = 10 / 0  # 0으로 나누기 시도
except ZeroDivisionError as e:
    print(f"오류 발생: {e}")
else:
    print("예외가 발생하지 않았습니다.")
finally:
    print("예외 처리 완료.")

 

설명: 0으로 나누기를 시도하면 ZeroDivisionError가 발생하고, 예외 처리 코드가 실행됩니다.

 

 

예시 2: 파일 열기 예외 처리

try:
    with open("non_existent_file.txt", "r") as f:
        content = f.read()
except FileNotFoundError as e:
    print(f"파일을 찾을 수 없습니다: {e}")
else:
    print("파일 읽기 성공")
finally:
    print("파일 처리 완료.")

설명: non_existent_file.txt라는 파일이 존재하지 않아 FileNotFoundError가 발생합니다. 예외 처리에서 오류 메시지가 출력됩니다.

 

 

예시 3: 사용자 입력에 대한 예외 처리

try:
    number = int(input("숫자를 입력하세요: "))  # 숫자로 변환
except ValueError as e:
    print(f"유효하지 않은 숫자 입력: {e}")
else:
    print(f"입력한 숫자는 {number}입니다.")
finally:
    print("입력 처리 완료.")

설명: 사용자가 숫자가 아닌 값을 입력할 경우 ValueError가 발생하고 예외 처리 코드가 실행됩니다.

 

예외 처리에서 주의할 점

  • 구체적인 예외를 처리하는 것이 좋습니다. except Exception처럼 모든 예외를 처리하면, 어떤 오류가 발생했는지 추적하기 어려울 수 있습니다.
  • 예외 처리 코드 내에서 불필요한 로직을 작성하지 않도록 하여 코드가 복잡해지지 않도록 합니다.
  • finally 구문은 자원 해제나 파일 닫기 등, 반드시 실행되어야 하는 코드에 유용합니다.

이러한 예외 처리 기법을 활용하면 프로그램이 예기치 않은 오류로 종료되지 않고 안정적으로 동작할 수 있습니다.

반응형

'프로그래밍 > [ Python ]' 카테고리의 다른 글

[Python] JSON 처리  (0) 2024.11.15
[Python] map  (0) 2024.11.13
[Python] List Comprehension  (0) 2024.11.12
[Python] Files (파일 관련)  (1) 2024.02.26
[python] pass, continue, break 활용법  (0) 2021.06.20
반응형

map() 함수는 주어진 함수를 iterable의 각 항목에 적용하여 새로운 iterable을 반환하는 함수입니다. 특히 반복문을 사용할 때 보다 간결한 코드로 변환할 수 있어 유용합니다.

 

map() 함수

map() 함수는 다음과 같은 구문을 가집니다:

map(function, iterable)

 

 

  • function: 각 항목에 적용할 함수.
  • iterable: 반복 가능한 객체(리스트, 튜플 등).

map() 함수는 function을 iterable의 모든 요소에 적용하고, 그 결과를 새로운 iterable로 반환합니다. 결과는 map 객체로 반환되므로, 이를 리스트나 다른 자료형으로 변환해야 사용할 수 있습니다.

 

 

예시 1: 리스트의 모든 값을 제곱하기

map() 함수와 lambda 함수를 사용하여 리스트의 모든 값을 제곱할 수 있습니다.

numbers = [1, 2, 3, 4, 5]
squares = map(lambda x: x ** 2, numbers)
print(list(squares))

 

 

출력:

[1, 4, 9, 16, 25]

 

 

예시 2: 문자열 리스트에서 대문자로 변환하기

map()을 사용하여 문자열 리스트의 모든 요소를 대문자로 변환할 수 있습니다.

words = ["apple", "banana", "kiwi"]
uppercase_words = map(str.upper, words)
print(list(uppercase_words))

 

출력:

['APPLE', 'BANANA', 'KIWI']

 

 

예시 3: 두 리스트의 각 항목 더하기

map()을 사용하여 두 개의 리스트에서 같은 위치의 항목을 더할 수 있습니다. 이때 zip()과 결합하여 사용할 수 있습니다.

list1 = [1, 2, 3]
list2 = [4, 5, 6]
summed = map(lambda x, y: x + y, list1, list2)
print(list(summed))

 

출력:

[5, 7, 9]

 

 

요약

  • map() 함수는 주어진 함수를 iterable의 각 요소에 적용할 때 유용합니다.
  • 코드가 간결해지고, 반복문을 사용하지 않고도 같은 작업을 할 수 있어 가독성이 높아집니다.
  • lambda 함수와 결합하여 짧고 효율적으로 사용할 수 있습니다.
반응형

'프로그래밍 > [ Python ]' 카테고리의 다른 글

[Python] JSON 처리  (0) 2024.11.15
[Python] 예외처리 try, except  (0) 2024.11.14
[Python] List Comprehension  (0) 2024.11.12
[Python] Files (파일 관련)  (1) 2024.02.26
[python] pass, continue, break 활용법  (0) 2021.06.20
반응형

파이썬에서 자주 사용하는 기능 중 하나는 리스트 컴프리헨션(List Comprehension) 입니다. 이 기능은 기존의 리스트를 기반으로 새로운 리스트를 간결하고 효율적으로 생성할 수 있게 해줍니다.

# 1부터 10까지의 숫자 중 짝수만 추출하여 새로운 리스트 생성
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = [x for x in numbers if x % 2 == 0]

print(even_numbers)  # 출력: [2, 4, 6, 8, 10]

 

이 코드에서는 x for x in numbers if x % 2 == 0를 사용하여 리스트의 각 요소를 순회하면서 짝수인 값만 필터링하여 새로운 리스트를 만듭니다. 리스트 컴프리헨션을 사용하면 for문을 이용해 새로운 리스트를 만드는 것보다 코드가 간결하고 읽기 쉬워집니다.

 

리스트 컴프리헨션을 활용한 다양한 예시를 소개할게요. 각각의 예시는 파이썬에서 자주 사용되는 패턴을 기반으로 만들었습니다.

 

 

1. 1부터 20까지의 숫자 중 제곱수가 50 이하인 숫자 리스트 만들기

squares = [x**2 for x in range(1, 21) if x**2 <= 50]
print(squares)  # 출력: [1, 4, 9, 16, 25, 36, 49]

 

여기서는 1부터 20까지의 숫자 중 제곱한 값이 50 이하인 숫자들만 포함하는 리스트를 생성합니다.

 

 

2. 문자열 리스트에서 길이가 5 이상인 단어만 필터링하기

words = ['apple', 'bat', 'banana', 'cherry', 'date']
long_words = [word for word in words if len(word) >= 5]
print(long_words)  # 출력: ['apple', 'banana', 'cherry']

 

이 예시에서는 문자열 리스트에서 길이가 5 이상인 단어들만 뽑아내는 리스트 컴프리헨션을 사용했습니다.

 

 

3. 주어진 리스트에서 각 숫자에 10을 더한 값으로 새로운 리스트 만들기

numbers = [1, 2, 3, 4, 5]
incremented_numbers = [x + 10 for x in numbers]
print(incremented_numbers)  # 출력: [11, 12, 13, 14, 15]

 

이 코드는 주어진 numbers 리스트의 각 숫자에 10을 더하여 새로운 리스트를 만드는 예시입니다.

반응형

'프로그래밍 > [ Python ]' 카테고리의 다른 글

[Python] 예외처리 try, except  (0) 2024.11.14
[Python] map  (0) 2024.11.13
[Python] Files (파일 관련)  (1) 2024.02.26
[python] pass, continue, break 활용법  (0) 2021.06.20
[연결 리스트] 두 수의 덧셈  (0) 2021.03.03
반응형

이번 포스팅에서는 파이썬으로 파일을 읽고, 쓰기 등 간단한 파일 관련된 코드에 대해 정리 해보겠습니다.

 

 

Reading

파일 읽기는 with open 구문으로 읽고자하는 파일을 지정한 후 read() 구문을 이용해서 할 수 있습니다.

with open('file.txt', 'r') as file:
    content = file.read()
    print(content)

 

 

Writing

파일 읽기와 마찬가지로 작성하고자 하는 파일 명을 with open 구문으로 저정한 후 write()를 활용하면 됩니다.

with open('file.txt', 'w') as file:
    file.write('Hello!')

 

 

Appending

이미 있는 파일에 붙여쓰기를 하기 위해서는 'a' 옵션으로 파일을 지정한 후 write()를 사용하게 되면 기존에 파일 내용에 원하는 내용을 추가할 수 있습니다.

with open('file.txt', 'a') as file:
    file.write('\nAppend')

 

 

Reading Lines

파일을 읽을 때 한 줄을 한번에 읽어오고 싶을때 readlines()를 사용하면 한 줄을 리스트 형태로 읽어올 수 있습니다.

with open('file.txt', 'r') as file:
    lines = file.readlines()
    print(lines)

 

 

Checking File Exists

파일을 읽거나 생성하기 전에 아래와 같이 파일의 존재유무를 체크할 수 있습니다.

import os
if os.path.exists('file.txt'):
    print('File exists.')
else:
    print('File does not exist.')

 

 

Multiple Files

with 구문을 활용하여 여러개의 파일을 동시에 활용할 수도 있습니다.

with open('original.txt', 'r') as first, open('copy.txt', 'w') as second:
    first_content = first.read()
    second.write(first_content)

 

 

Deleting

os의 remove를 활용하여 파일을 삭제할수도 있습니다.

import os
if os.path.exists('file.txt'):
    os.remove('file.txt')
    print('File deleted.')
else:
    print('File does not exist.')
반응형

'프로그래밍 > [ Python ]' 카테고리의 다른 글

[Python] map  (0) 2024.11.13
[Python] List Comprehension  (0) 2024.11.12
[python] pass, continue, break 활용법  (0) 2021.06.20
[연결 리스트] 두 수의 덧셈  (0) 2021.03.03
[연결 리스트] 역순 연결 리스트  (0) 2021.01.25
반응형

브라우저란?

브라우저는 사람들이 가장 많이 사용되는 소프트웨어입니다. 인터넷 익스플로러, 크롬, 파이어폭스, 사파리 등 브러우저를 통해 우리는 주소창에 서버 도메인을 입력하여 손쉽게 인터넷 서비스를 이용합니다.

 

브라우저의 주요 기능은 사용자가 선택한 자원을 서버에 요청하고 이를 화면에 표시해주는 것 입니다. 여기서 자원은 보통 HTML 문서를 뜻하지만 PDF나 이미지 등의 다른 형태일 수도 있습니다. 사용자는 URI(Uniform Resource Identifier)에 의해 정해진 자원의 주소를 이용해서 요청할 수 있습니다.

 

 

브라우저의 기본 구조

우리가 사용하는 브라우저는 다음과 같은 요소들로 구성되어 있습니다.

 

브라우저의 주요 요소

사용자 인터페이스

- 사용자가 접근할 수 있는 영역으로, URI를 입력하는 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등 사용자가 요청한 페이지를 보여주는 창을 제외한 나머지 부분입니다.

 

브라우저 엔진

- 사용자 인터페이스와 렌더링 사이의 동작을 제어합니다. 또한 데이터 저장소를 참조하여 로컬에 데이터를 읽고 쓰면서 다양한 작업을 합니다.

 

렌더링 엔진

- 서버로부터 응답 받은 자원을 브라우저에 표시합니다. 예로, 서버로부터 HTML 문서를 응답 받으면 HTML과 CSS를 파싱하여 브라우저 화면에 표시하는 역할을 합니다.

 

통신

- HTTP 요청과 같이 서버와 통신이 가능하게 하는 네트워크 호출에 사용됩니다. 

 

자바스크립트 해석기

- 자바스크립트 코드를 해석하고 실행합니다.

 

UI 백엔드

- 기본적인 장치(콤보 박스, 창 등)를 그립니다. 

 

자료 저장소

- 쿠키와 같은 모든 종류의 자원을 하드에 저장하는 역할을 합니다.

 

 

 

렌더링 엔진

브라우저의 동작을 이해하는데 중요한 부분이 바로 렌더링 엔진입니다. 렌더링 엔진은 요청 받은 내용을 브라우저 화면에 표시하는 역할을 수행합니다.

 

HTML, XML 그리고 이미지를 표시할 수 있으며 추가로 플러그인이나 확장 기능을 이용하여 PDF와 같은 다른 유형의 문서도 표시가 가능합니다. 본 포스팅에서는 기본적인 HTML과 CSS 처리에 대해서 정리하도록 하겠습니다.

 

렌더링 엔진에는 다양한 종류가 있습니다.

- Blink: 크롬, 오페라

- Webkit: 사파리

- Trident: 익스플로어

- EdgeHTML: 엣지

 

 

렌더링 엔진 동작 과정

 

렌더링 엔진 동작 과정

 

렌더링 엔진은 HTML 문서를 파싱하고 콘텐츠 트리 내부에서 태그를 DOM 노드로 변환합니다. 그 다음 외부 CSS 파일과 함께 포함된 스타일 요소를 파싱합니다. 이때, 스타일 정보와 HTML 표시 규칙은 렌더 트리라고 하는 또 다른 트리를 생성합니다.

 

아래 그림은 렌더링 엔진 중 하나인 Webkit의 상세 동작 과정입니다.

Webkit 동작 과정

 

위 두 가지 그림을 보면 동작 과정이 거의 동일한 것을 확인할 수 있습니다.

 

세부 동작 과정

1. HTML 문서 파싱후 DOM(Document Object Model) 트리 구축

DOM은 HTML 문서 객체의 표현이고 자바스크립트와 같은 HTML 요소의 인터페이스입니다. DOM은 마크업과 1:1 관계를 맺습니다. 아래는 마크업과 매칭되는 DOM 트리의 예시 입니다.

 <html>
  <body>
   <p>Hello World</p>
   <div><img src="example.png" /></div>
  </body>
</html>

마크업의 DOM 트리

 

2. CSS 파싱

CSS 파싱

 

3. 렌더 트리 생성

렌더 트리는 문서를 시각적인 구성 요소로 만들어주는 역할을 합니다.

 

4. 렌더 트리 배치 (레이아웃)

렌드 트리는 위치와 크기에 대한 정보를 가지고 있지 않기 때문에, 브라우저 화면의 어느 공간에 위치할지 각 객체들에게 위치와 크기를 결정하는 과정을 거칩니다.

 

5. 렌더 트리 그리기

렌더 트리가 만들어지고 배치가 구성되면 UI 백엔드가 렌더 트리의 각 객체를 화면에 구성된 배치에 맞게 그립니다.

 

 

 

(참조)

브라우저는 어떻게 동작하는가?

How Browsers Work: Behind the scenes of modern web browsers

[CS] 웹 브라우저는 어떻게 동작하는가?

반응형

'프로그래밍 > [ 백엔드 로드맵 ]' 카테고리의 다른 글

[인터넷] HTTP란?  (0) 2022.04.21
[인터넷] - 인터넷의 작동 원리  (0) 2022.04.21
백엔드 로드맵  (0) 2022.04.20
반응형

HTTP (HyperText Transfer Protocol)

서버와 클라이언트가 인터넷에서 데이터를 주고 받기 위한 프로토콜입니다.

 

HTTP 동작 방식

 

 

HTTP는 서버 / 클라이언트 모델을 따릅니다.

클라이언트가 서버에 요청을 보내면 서버는 요청에 맞는 응답을 클라이언트에게 보냅니다.

  1. connect: 클라이언트가 원하는 서버에 접속
  2. request: 클라이언트가 서버에게 원하는 요청을 보냄
  3. response: 서버가 요청에 대한 결과를 클라이언트에게 보내고 응답
  4. close: 응답이 끝나면 서버와 클라이언트 연결 종료 (Stateless)

 

 

HTTP 특징

  • TCP/IP를 이용하는 응용 프로토콜이다.
  • 비연결성 프로토콜이다. 따라서 클라이언트의 이전 상태를 서버가 알 수 없다는 단점이 있다.
    (이를 해결하기 위해 cookie와 session이 등장하였다.)
  • 비연결성 프로토콜이기 때문에 요청 / 응답 방식으로 동작한다.
  • 서버: 데이터 접근을 관리하는 네트워크 시스템
  • 클라이언트: 데이터에 접근할 수 있는 프로그램 ( 웹 브라우저, 핸드폰 어플리케이션 등..)

* 예를 들어 사용자가 회원가입을 시도할 때, 클라이언트 프로그램에서 서버로 회원정보를 보내고 서버는 이를 저장한다. 이 과정에서 HTTP 프로토콜을 이용한 클라이언트-서버 간의 교류가 이루어진다.

 

Request

- 클라이언트가 서버에게 요청할 정보를 담아 보낸다.

- Request Method에는 GET, POST, PUT, DELETE가 있다.

 

Response

- 서버가 요청에 대한 답변을 클라이언트에게 보낸다.

 

 

(참조)

1) HTTP란 무엇인가

2) 웹의 동작 (HTTP 프로토콜 이해)

반응형
반응형

인터넷이 무엇인지는 다들 알고 있을거라 생각합니다. 그렇다면 우리가 사용하는 인터넷이 어떻게 구성되어 있으며 어떻게 동작하는지에 대해서 알아보도록 하겠습니다.

 

 

TCP/IP

TCP/IP는 컴퓨터와 컴퓨터간의 통신을 위한 규약으로 정의할 수 있습니다. 2개의 프로토콜로 이루어져있으며 IP 프로토콜 위에 TCP 프로토콜이 놓여있습니다.

 

IP

네트워크상 컴퓨터의 고유 주소입니다. 192.168.2.1 같은 형식의 총 4바이트로 이루어져 있습니다.

 

TCP

클라이언트와 서버간 데이터를 신뢰성있게 전달하기 위해 만들어진 프로토콜입니다. 데이터 교환을 안정적으로 순서대로 처리할 수 있게하여 에러를 방지합니다.

 

인터넷이란, 각 컴퓨터들간 TCP/IP 통신 프로토콜을 이용하여 서로 데이터를 주고 받도록 구성된 네트워크입니다.

 

 

네트워크

가장 심플하게 2대의 컴퓨터가 통신을 하기 위해서는 유선(케이블) 또는 무선(WiFi, Bluetooth)로 연결하면 됩니다.

 

컴퓨터 2대 네트워크

 

하지만 이런 방식은 아래처럼 여러대의 컴퓨터 네트워크를 구성하기에는 현실적으로 제한사항이 많습니다.

 

여러대 컴퓨터 네트워크..

이러한 문제를 해결하기 위해 라우터를 사용하게 됩니다. 라우터는 특수한 소형 컴퓨터로 데이터를 원하는 컴퓨터한테 잘 전달해주는 역할을 합니다. 

 

라우터 또한 컴퓨터이기 때문에 아래와 같이 라우터끼리 연결해서 네트워크를 더욱 확장할 수 있습니다.

 

라우터 - 라우터를 통한 네트워크 확장

이제 아주 먼 곳에 있는 컴퓨터끼리의 네트워크 구성 문제를 해결해야 합니다. 물리적으로 거리가 먼 곳에는 케이블 연결이 힘들기 때문입니다. 이를 해결하기 위해 사용되는 것이 모뎀입니다. 이미 우리는 전력 및 전화와 같은 인프라가 구성되어 있습니다. 모뎀은 우리 네트워크에서 교환하는 데이터를 전화 시설에서 처리 할 수 있는 정보로 바꾸어주는 역할을 함으로써 이미 구성되어 있는 케이블을 사용하여 멀리 떨어진 네트워크끼리 연결하여 더욱 확장할 수 있게 해줍니다.

 

모뎀을 이용한 네트워크 확장

 

마지막 구성요소로 ISP가 있습니다. 인터넷 서비스 제공업체의 ISP는 데이터를 네트워크와 네트워크 중간에서 전달해주는 역할을 하며 한국에서는 LG U+, KT, SKT등이 이를 관리합니다.

 

전체 인터넷 구성

 

 

(참조)

[IT 기술] 인터넷의 작동원리

반응형

'프로그래밍 > [ 백엔드 로드맵 ]' 카테고리의 다른 글

[인터넷] 브라우저와 그 작동 원리  (0) 2022.04.22
[인터넷] HTTP란?  (0) 2022.04.21
백엔드 로드맵  (0) 2022.04.20

+ Recent posts