티스토리 뷰
지난 포스팅 : Python의 Requests Library의 동작 Part1 에서 이어집니다.
https://teus-kiwiee.tistory.com/160
지난 포스팅까지 해서 HTTPAdapter Class에 대해서 확인하는 과정에서 urllib3이 나왔고, Requests Package를 벗어나는 영역이라 2번째 Part로 나눴습니다.
현재까지 상황을 정리하면 아래와 같습니다.
Requests.get에서 request()에 Message전달
-> Requests.request에서 Requests.Session Object에 Message전달
-> Requests.Session.request에서 Request Object 생성
-> Requests.Session.prepare_request에 Message전달
-> Requests.Session의 prep로 결과를 받고, Requests.Session.send에 Message전달
-> Requests.Session.send에서 Requests.Session.get_adapter로 Message전달
-> HTTPAdapter Object가 필요함
지난번, 아래 requests/adapters의 HTTPAdapter Class의 send method에서 conn을 획득해야 했고
이 conn은 HTTPAdapter Class의 get_connection method로부터 받아왔습니다.
출처 : requests/requests/adapters.py#436
def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
try:
conn = self.get_connection(request.url, proxies)
except LocationValueError as e:
raise InvalidURL(e, request=request)
...
def get_connection(self, url, proxies=None):
proxy = select_proxy(url, proxies)
if proxy:
proxy = prepend_scheme_if_needed(proxy, "http")
proxy_url = parse_url(proxy)
if not proxy_url.host:
raise InvalidProxyURL(
"Please check proxy URL. It is malformed "
"and could be missing the host."
)
proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_url(url)
else:
# Only scheme should be lower case
parsed = urlparse(url)
url = parsed.geturl()
conn = self.poolmanager.connection_from_url(url)
return conn
def init_poolmanager(
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
):
self._pool_connections = connections
self._pool_maxsize = maxsize
self._pool_block = block
self.poolmanager = PoolManager(
num_pools=connections,
maxsize=maxsize,
block=block,
strict=True,
**pool_kwargs,
)
반환하는 conn은 PoolManager Object의 method의 반환값인 것을 알 수 있고, 해당 객체는 urllib3에 존재하는 class입니다.
그럼, 이제 회고는 끝내고 urllib3으로 들어가 보겠습니다.
출처 : urllib3/src/urllib3/poolmanager.py#172
class PoolManager(RequestMethods):
proxy: Optional[Url] = None
proxy_config: Optional[ProxyConfig] = None
def __init__(
self,
num_pools: int = 10,
headers: Optional[Mapping[str, str]] = None,
**connection_pool_kw: Any,
) -> None:
super().__init__(headers)
self.connection_pool_kw = connection_pool_kw
def dispose_func(p: Any) -> None:
p.close()
self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool]
self.pools = RecentlyUsedContainer(num_pools, dispose_func=dispose_func)
self.pool_classes_by_scheme = pool_classes_by_scheme
self.key_fn_by_scheme = key_fn_by_scheme.copy()
def connection_from_url(
self, url: str, pool_kwargs: Optional[Dict[str, Any]] = None
) -> HTTPConnectionPool:
u = parse_url(url)
return self.connection_from_host(
u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs
)
def connection_from_host(
self,
host: Optional[str],
port: Optional[int] = None,
scheme: Optional[str] = "http",
pool_kwargs: Optional[Dict[str, Any]] = None,
) -> HTTPConnectionPool:
if not host:
raise LocationValueError("No host specified.")
request_context = self._merge_pool_kwargs(pool_kwargs)
request_context["scheme"] = scheme or "http"
if not port:
port = port_by_scheme.get(request_context["scheme"].lower(), 80)
request_context["port"] = port
request_context["host"] = host
return self.connection_from_context(request_context)
def connection_from_context(
self, request_context: Dict[str, Any]
) -> HTTPConnectionPool:
if "strict" in request_context:
warnings.warn(
"The 'strict' parameter is no longer needed on Python 3+. "
"This will raise an error in urllib3 v3.0.0.",
DeprecationWarning,
)
request_context.pop("strict")
scheme = request_context["scheme"].lower()
pool_key_constructor = self.key_fn_by_scheme.get(scheme)
if not pool_key_constructor:
raise URLSchemeUnknown(scheme)
pool_key = pool_key_constructor(request_context)
return self.connection_from_pool_key(pool_key, request_context=request_context)
def connection_from_pool_key(
self, pool_key: PoolKey, request_context: Dict[str, Any]
) -> HTTPConnectionPool:
with self.pools.lock:
# If the scheme, host, or port doesn't match existing open
# connections, open a new ConnectionPool.
pool = self.pools.get(pool_key)
if pool:
return pool
# Make a fresh ConnectionPool of the desired type
scheme = request_context["scheme"]
host = request_context["host"]
port = request_context["port"]
pool = self._new_pool(scheme, host, port, request_context=request_context)
self.pools[pool_key] = pool
return pool
일단 순서대로.
- connection_from_url에서 url을 parsing하고, url을 host, port, scheme 단위로 쪼갭니다.
- connection_from_host에서 host정보를 한번 검토하고, 정상이면 host와 port, scheme정보를 request context에 업데이트 시킵니다.
- connection_from_context에서 pool_key_constructor를 통해서 connection pool을 만들기 위한 context를 준비시킵니다.(PoolKey Class)
- connection을 위한 pool을 생성하고, 이 pool 을 반환합니다.
이때, 4번의 pool에 대해서 좀더 확인해 보겠습니다.
출처 : urllib3/src/urllib3/poolmanager.py#242
def _new_pool(
self,
scheme: str,
host: str,
port: int,
request_context: Optional[Dict[str, Any]] = None,
) -> HTTPConnectionPool:
pool_cls: Type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme]
if request_context is None:
request_context = self.connection_pool_kw.copy()
if request_context.get("blocksize") is None:
request_context["blocksize"] = _DEFAULT_BLOCKSIZE
for key in ("scheme", "host", "port"):
request_context.pop(key, None)
if scheme == "http":
for kw in SSL_KEYWORDS:
request_context.pop(kw, None)
return pool_cls(host, port, **request_context)
...
pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool}
self.pool_classes_by_scheme = pool_classes_by_scheme
poolmanager의 _new_pool method에서 기존에 context를 받고
이 context의 schem, host, port부분을 일부 수정한 다음에 HTTP/HTTPSConnectionPool Object를 만들어 반환하는 것을 볼 수 있습니다.
출처 : urllib3/src/urllib3/connectionpool.py#116
class HTTPConnectionPool(ConnectionPool, RequestMethods):
"""
Thread-safe connection pool for one host.
"""
scheme = "http"
ConnectionCls: Type[Union[HTTPConnection, HTTPSConnection]] = HTTPConnection
ResponseCls = HTTPResponse
def __init__(
self,
host: str,
port: Optional[int] = None,
timeout: Optional[_TYPE_TIMEOUT] = _DEFAULT_TIMEOUT,
maxsize: int = 1,
block: bool = False,
headers: Optional[Mapping[str, str]] = None,
retries: Optional[Union[Retry, bool, int]] = None,
_proxy: Optional[Url] = None,
_proxy_headers: Optional[Mapping[str, str]] = None,
_proxy_config: Optional[ProxyConfig] = None,
**conn_kw: Any,
):
ConnectionPool.__init__(self, host, port)
RequestMethods.__init__(self, headers)
if not isinstance(timeout, Timeout):
timeout = Timeout.from_float(timeout)
if retries is None:
retries = Retry.DEFAULT
self.timeout = timeout
self.retries = retries
self.pool: Optional[queue.LifoQueue[Any]] = self.QueueCls(maxsize)
self.block = block
self.proxy = _proxy
self.proxy_headers = _proxy_headers or {}
self.proxy_config = _proxy_config
# Fill the queue up so that doing get() on it will block properly
for _ in range(maxsize):
self.pool.put(None)
# These are mostly for testing and debugging purposes.
self.num_connections = 0
self.num_requests = 0
self.conn_kw = conn_kw
if self.proxy:
self.conn_kw.setdefault("socket_options", [])
self.conn_kw["proxy"] = self.proxy
self.conn_kw["proxy_config"] = self.proxy_config
결국 HTTPAdapter Class의 get_connections의 반환값으로 requests Context로 만들어진 HTTPConnectionPool Object가 만들어 졌습니다.
이 HTTPConnectionPool같은 경우 ThreadSafe한 Connection을 Queue에 넣고, Pool 형태로 관리하는것을 확인할 수 있습니다.
이제 conn의 정체를 알았으니, 다시 requests/adapters.py의 send로 돌아가 보겠습니다.
출처 : requests/requests/adapters.py#436
def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
try:
conn = self.get_connection(request.url, proxies)
except LocationValueError as e:
raise InvalidURL(e, request=request)
self.cert_verify(conn, request.url, verify, cert)
url = self.request_url(request, proxies)
self.add_headers(
request,
stream=stream,
timeout=timeout,
verify=verify,
cert=cert,
proxies=proxies,
)
chunked = not (request.body is None or "Content-Length" in request.headers)
if isinstance(timeout, tuple):
try:
connect, read = timeout
timeout = TimeoutSauce(connect=connect, read=read)
except ValueError:
raise ValueError(
f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
f"or a single float to set both timeouts to the same value."
)
elif isinstance(timeout, TimeoutSauce):
pass
else:
timeout = TimeoutSauce(connect=timeout, read=timeout)
#==============요기까지 connection pool을 획득하고, Req Header를 정리======
try:
#==============Chunk Encoding이 적용되지 않은 경우======
if not chunked:
resp = conn.urlopen(
method=request.method,
url=url,
body=request.body,
headers=request.headers,
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=self.max_retries,
timeout=timeout,
)
#==============Chunk Encoding이 적용된 경우======
# Send the request.
else:
if hasattr(conn, "proxy_pool"):
conn = conn.proxy_pool
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
try:
skip_host = "Host" in request.headers
low_conn.putrequest(
request.method,
url,
skip_accept_encoding=True,
skip_host=skip_host,
)
for header, value in request.headers.items():
low_conn.putheader(header, value)
low_conn.endheaders()
for i in request.body:
low_conn.send(hex(len(i))[2:].encode("utf-8"))
low_conn.send(b"\r\n")
low_conn.send(i)
low_conn.send(b"\r\n")
low_conn.send(b"0\r\n\r\n")
# Receive the response from the server
r = low_conn.getresponse()
resp = HTTPResponse.from_httplib(
r,
pool=conn,
connection=low_conn,
preload_content=False,
decode_content=False,
)
이제 HTTP Header의 추가적인 정비작업을 하고, Chunk Encoding 여부에 따라서 다르게 Response를 받아오는 것을 알 수 있습니다.
(Chunk Encoding같은 경우 Body를 Chunk단위로 쪼개서 오기 때문에, 이 Chunk를 pool마다 따로따로 받아서 합치는 것을 유추해 볼 수가 있습니다)
이제 고지가 가까워 보입니다. 이제 저 urlopen의 반환 형식으로 BaseHTTPResponse 객체를 반환하고
Response를 받았다는 것은 Client <-> Server간 통신이 끝났다는것을 의미하니깐요!
그럼 HTTPConnectionPool.urlopen 소스코드를 살펴보겠습니다.
출처 : urllib3/src/urllib3/connectionpool.py#537
def urlopen( # type: ignore[override]
self,
method: str,
url: str,
body: Optional[_TYPE_BODY] = None,
headers: Optional[Mapping[str, str]] = None,
retries: Optional[Union[Retry, bool, int]] = None,
redirect: bool = True,
assert_same_host: bool = True,
timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
pool_timeout: Optional[int] = None,
release_conn: Optional[bool] = None,
chunked: bool = False,
body_pos: Optional[_TYPE_BODY_POSITION] = None,
**response_kw: Any,
) -> BaseHTTPResponse:
parsed_url = parse_url(url)
destination_scheme = parsed_url.scheme
if headers is None:
headers = self.headers
if not isinstance(retries, Retry):
retries = Retry.from_int(retries, redirect=redirect, default=self.retries)
if release_conn is None:
release_conn = response_kw.get("preload_content", True)
# Check host
if assert_same_host and not self.is_same_host(url):
raise HostChangedError(self, url, retries)
# Ensure that the URL we're connecting to is properly encoded
# target url의 query들을 encoding하고, 이 encoding된 결과는 최종적으로 to_str을 사용해서 python의 unicode형태로 반환해줌
if url.startswith("/"):
url = to_str(_encode_target(url))
else:
url = to_str(parsed_url.url)
conn = None
release_this_conn = release_conn
http_tunnel_required = connection_requires_http_tunnel(
self.proxy, self.proxy_config, destination_scheme
)
if not http_tunnel_required:
headers = headers.copy() # type: ignore[attr-defined]
headers.update(self.proxy_headers) # type: ignore[union-attr]
err = None
clean_exit = False
body_pos = set_file_position(body, body_pos)
try:
# Request a connection from the queue.
timeout_obj = self._get_timeout(timeout)
conn = self._get_conn(timeout=pool_timeout)
conn.timeout = timeout_obj.connect_timeout # type: ignore[assignment]
is_new_proxy_conn = self.proxy is not None and not getattr(
conn, "sock", None
)
if is_new_proxy_conn:
assert isinstance(self.proxy, Url)
conn._connecting_to_proxy = True
if http_tunnel_required:
try:
self._prepare_proxy(conn)
except (BaseSSLError, OSError, SocketTimeout) as e:
self._raise_timeout(
err=e, url=self.proxy.url, timeout_value=conn.timeout
)
raise
# Make the request on the httplib connection object.
httplib_response = self._make_request(
conn,
method,
url,
timeout=timeout_obj,
body=body,
headers=headers,
chunked=chunked,
)
이곳에서 확인할 수 있는것은, target url의 query 부분을 처리하는 부분이 추가되어있는 것을 알 수가 있습니다.
해당 소스코드의 아래 부분은 retry를 처리하기 위한 에러 처리 부분이고, 이제 HTTPConnectionPool.urlopen._make_request를 찾아가게 됩니다.
출처 : urllib3/src/urllib3/connectionpool.py#371
def _make_request(
self,
conn: HTTPConnection,
method: str,
url: str,
timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
chunked: bool = False,
**httplib_request_kw: Any,
) -> _HttplibHTTPResponse:
self.num_requests += 1
timeout_obj = self._get_timeout(timeout)
timeout_obj.start_connect()
conn.timeout = timeout_obj.connect_timeout # type: ignore[assignment]
'''
connection validation 부분
'''
try:
if chunked:
conn.request_chunked(method, url, **httplib_request_kw)
else:
conn.request(method, url, **httplib_request_kw)
여기서는 request의 timeout을 위한 time 설정 및 기존 만들어진 HTTPConnection이 정상인지 검토하고, HTTPConnection.request로 넘어가게 됩니다.
출처 : urllib3/src/urllib3/cocnnection.py#286
def request( # type: ignore[override]
self,
method: str,
url: str,
body: Optional[_TYPE_BODY] = None,
headers: Optional[Mapping[str, str]] = None,
chunked: bool = False,
) -> None:
if headers is None:
headers = {}
header_keys = frozenset(to_str(k.lower()) for k in headers)
skip_accept_encoding = "accept-encoding" in header_keys
skip_host = "host" in header_keys
self.putrequest(
method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host
chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize)
chunks = chunks_and_cl.chunks
content_length = chunks_and_cl.content_length
# When chunked is explicit set to 'True' we respect that.
if chunked:
if "transfer-encoding" not in header_keys:
self.putheader("Transfer-Encoding", "chunked")
else:
if "content-length" in header_keys:
chunked = False
elif "transfer-encoding" in header_keys:
chunked = True
else:
chunked = False
if content_length is None:
if chunks is not None:
chunked = True
self.putheader("Transfer-Encoding", "chunked")
else:
self.putheader("Content-Length", str(content_length))
if "user-agent" not in header_keys:
self.putheader("User-Agent", _get_default_user_agent())
for header, value in headers.items():
self.putheader(header, value)
#============= 실질적으로 request header가 완성되는 부분)
self.endheaders()
#Thread Safe를 위해서 self.__state를 _CS_REQ_STARTED -> SENT로 변경하고
#self._send_output() method로 HTTP Message를 전송함
'''
Chunk True일 경우 Chunk Message를 전송하는 부분
'''
여기서, putrequest같은 경우 super().putrequest를 사용하게 되고, 이 super().putrequest는 Python의 내장 Library인 http.client의 HTTPConnection.putrequest가 됩니다.
이때 Official Docs상 해당 부분이 sent를 한다고 되어 있으나, 사실 __state를 변경한 다음 HTTP Message를 생성하는 단계일뿐, 아직 Client -> Server로 HTTP Message가 전송되지는 않습니다.
실질적으로 HTTPConnection.endheaders에서 object의 state를 바꾸고, 이 다음 _send_output method를 호출하여 HTTP Message 전송을 시도하게 됩니다.
출처 : CPython/Lib/http/client.py#1266
def endheaders(self, message_body=None, *, encode_chunked=False):
if self.__state == _CS_REQ_STARTED:
self.__state = _CS_REQ_SENT
else:
raise CannotSendHeader()
self._send_output(message_body, encode_chunked=encode_chunked)
def _send_output(self, message_body=None, encode_chunked=False):
"""Send the currently buffered request and clear the buffer.
Appends an extra \\r\\n to the buffer.
A message_body may be specified, to be appended to the request.
"""
self._buffer.extend((b"", b""))
msg = b"\r\n".join(self._buffer)
del self._buffer[:]
self.send(msg)
'''
이 이하는 chunk 전송 시 처리
'''
이제 거의 끝이 보입니다. endheaders부분까지 HTTP Requests Message의 검증이 완료된 상태가 되었고, 이 Message는 HTTPConnection.send method를 통해서 전송이 되게 됩니다.
출처 : Cpython/Lib/http/client.py#967
def send(self, data):
if self.sock is None:
#self.sock 이 없을경우, self.connect를 통해서 self.sock을 다시 정의해줌
if self.auto_open:
self.connect()
else:
raise NotConnected()
if self.debuglevel > 0:
print("send:", repr(data))
if hasattr(data, "read") :
if self.debuglevel > 0:
print("sendIng a read()able")
encode = self._is_textIO(data)
if encode and self.debuglevel > 0:
print("encoding file using iso-8859-1")
while 1:
datablock = data.read(self.blocksize)
if not datablock:
break
if encode:
datablock = datablock.encode("iso-8859-1")
sys.audit("http.client.send", self, datablock)
self.sock.sendall(datablock)
return
sys.audit("http.client.send", self, data)
try:
self.sock.sendall(data)
except TypeError:
if isinstance(data, collections.abc.Iterable):
for d in data:
self.sock.sendall(d)
else:
raise TypeError("data should be a bytes-like object "
"or an iterable, got %r" % type(data))
def connect(self):
"""Connect to the host and port specified in __init__."""
sys.audit("http.client.connect", self, self.host, self.port)
#self.sock이 없을 때 새로 정의해 주는 부분
self.sock = self._create_connection(
(self.host,self.port), self.timeout, self.source_address)
...
드디어 다 왔습니다.
결국 HTTP Message를 보내는 순간에 socket의 존재 여부를 확인하고, 없을 경우 self.connect를 실행하여 self.sock을 다시 정의하는것을 볼 수가 있습니다.
출처 : Cpython/lib/socket.py#808
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
host, port = address
err = None
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
af, socktype, proto, canonname, sa = res
sock = None
try:
#=================실제 socket이 만들어 지는 부분========================#
sock = socket(af, socktype, proto)
if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
sock.connect(sa)
# Break explicitly a reference cycle
err = None
return sock
#====만들어진 socket을 target Address에다 Bind후 Socket OBject를 반환===#
except error as _:
err = _
if sock is not None:
sock.close()
if err is not None:
try:
raise err
finally:
# Break explicitly a reference cycle
err = None
else:
raise error("getaddrinfo returns an empty list")
결국, 내부에서 socket을 만들고, 이 socket을 반환한 다음 외부 url과 HTTP Message를 주고받는 것을 확인할 수 있습니다!
이제, 두개의 포스팅에 걸친 과정을 정리해보면 아래와 같습니다.
Requests.get에서 request()에 Msg 전달
-> Requests.request에서 Requests.Session Object에 Msg 전달
-> Requests.Session.prepare_request에 Msg 전달
-> PreparedRequest Object 생성 후 Requests.Session로 복귀
-> Requests.Session.send에 Msg 전달
-> Requests.Session.get_adapter로 Msg 전달
-> HTTPAdapter Object를 생성 후 Requests.Session.send로 복귀
-> HTTPAdapter.send에 Msg 전달
-> HTTPAdapter.get_connection에 Msg 전달
-> HTTPAdapter.poolmanager(urllib3.PoolManager Object).connection_from_url에 Msg 전달
-> HTTPAdapter.poolmanager.connection_from_host에 Msg전달
-> HTTPAdapter.poolmanager.connection_from_context에 Msg 전달
-> HTTPAdapter.poolmanager.connection_from_pool_key에 Msg 전달
-> HTTPAdapter.poolmanager._new_pool에 Msg 전달
-> urllib3.HTTPConnectionPool Class Object(=conn) 생성 후 반환
-> conn Object를 생성 후 HTTPAdapter.send 로 복귀
-> conn.urlopen에 Msg 전달
-> conn._make_request에 Msg 전달
-> HTTPconnection.request에 Msg 전달
-> HTTPconnection.endheaders에 Msg 전달
-> HTTPconnection._send_output에 Msg 전달
-> HTTPconnection.send에 Msg 전달
-> HTTPconnection.connect에 Msg 전달
-> HTTPconnection._create_connection에 Msg 전달
-> HTTPconnection._create_connection 내부에서 socket 을 생성하고, 외부로 전달 후 통신 진행
결국 복잡한 내부의 과정을 거쳤지만,
Socket통신을 하기 전까지 HTTP Message를 정비하고,
HTTP Message Header에 명시된 동작(Transfer Encoding, Retry...)을 수행하기 위해서 다양한 작업들이 사전에 수행 되는것을 볼 수 있습니다.
그리고, 모든 준비가 완료되었을 때 HTTP Message를 Socket수준의 Low Level 통신을 통해서 외부와 통신을 하는것을 볼 수 있습니다 :)
이제 맨 첫 예제의 HTTP Message가 어떻게 이뤄져 있는지 확인해 보겠습니다.
requests가 어떻게 동작하는지 알았으니, Cpython의 http.client.py에서 print를 추가하면
Bytes형태로 Socket으로 보내지기 직전의 HTTP Message를 볼 수가 있습니다
def send(self, data):
if self.sock is None:
if self.auto_open:
self.connect()
else:
raise NotConnected()
#output stream을 추가하여, 소켓으로 보내는 HTTP Message를 확인
print(data)
import requests
r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>b'GET /user HTTP/1.1\r\nHost: api.github.com\r\nUser-Agent: python-requests/2.27.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Basic --------\r\n\r\n'
"""
requests 예제가 실제 동작할때 사용된 HTTP Message
GET /user HTTP/1.1
Host: api.github.com
User-Agent: python-requests/2.27.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Authorization: Basic --------
"""
'Python 잡지식 > 소스코드 톱아보기' 카테고리의 다른 글
[Pandas]inplace=True동작에 대해서 (0) | 2023.12.06 |
---|---|
[Pandas]Series의 구조에 대해서 알아보자2 (1) | 2023.12.06 |
[Pandas]Series의 구조에 대해서 알아보자1 (0) | 2023.12.06 |
[Python]Requests Library의 동작 Part1 (0) | 2022.08.12 |
[CPython]CPython의 List 구현 살펴보기 (0) | 2022.02.16 |
- Total
- Today
- Yesterday
- GDC
- 컴퓨터그래픽스
- AVX
- 알고리즘
- 프로그래머스
- Search알고리즘
- git
- Sort알고리즘
- 완전탐색 알고리즘
- 병렬처리
- C++
- 사칙연산
- 분할정복
- SIMD
- 동적계획법
- 자료구조
- stack
- Python
- heap
- hash
- prime number
- 코딩테스트
- Greedy알고리즘
- 이분탐색
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |