Python Language

Types

Singleton Objects

  • Ellipsis or ... literals

  • NotImplemented object

  • None

The Ellipsis object’s meaning is up to the API that uses it, here are two common ones.

from typing import Tuple, Callable

a = ...
b = Ellipsis

print(a is b)  # True

#  Arbitrary-length homogeneous tuples can be expressed
#  using one type and ellipsis, for example
t = Tuple[int, ...]


def partial(func: Callable[..., str], *args) -> Callable[..., str]:
    """
    It is possible to declare the return type of a callable without specifying the call
    signature by substituting a literal ellipsis (three dots) for the list of arguments
    """
    ...  # Ellipsis is also expression and a statement

The NotImplemented Object is another singleton that is returned from comparisons and binary operations when they are asked to operate on types they don’t support. It is not an Exception

The Null Type, None is a singleton object.

For these three singleton objects, identity check should be used with is and is not

a = None
b = None

print(a is b)

Loops and iterators

The for else construct. The else clause only executed upon full and successful completion of the iteration without premature breaking or Exception raised

for x in range(5):
    print(x)
    if x == 5:
        break
    else:
        continue
else:
    print('done!')

The while else construct, The else block works similarly to the for else construct

c = 0
while c < 3:
    print(c)
    c += 1
else:
    print('complete!')

Try Clause

For catching generic or specific exceptions, and for proper resource cleanup and finalization. The first example should print 1 3 4, and the second should print 2 4

try:
    print("1")
except RuntimeError as ex:
    print("2")
else:
    print("3")
finally:
    print("4")


try:
    raise RuntimeError()
    print("1")
except RuntimeError as ex:
    print("2")
else:
    print("3")
finally:
    print("4")

Scope

global and nonlocal

# global scope
a = 1
b = 1

def work():
    a = 5
    global b
    b = 5

print(a, b)
def work():
    x = 1 # local scope
    y = 1

    def other_work():
        x = 2 # local scope
        nonlocal y
        y = 2

    other_work()
    print(x, y)

work()

Function Definition

Function parameters

https://docs.python.org/3/tutorial/controlflow.html#function-examples

def standard_arg(arg):
print(arg)

def pos_only_arg(arg, /):
    print(arg)

def kwd_only_arg(*, arg):
    print(arg)

def combined_example(pos_only, /, standard, *, kwd_only):
    print(pos_only, standard, kwd_only)

Datetime

Formatting and Parsing docs:

https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes

Construction and TZ Conversions

import datetime as dtx
from zoneinfo import ZoneInfo

tz_utc = ZoneInfo('UTC')
tz_est = ZoneInfo('US/Eastern')
tz_local = ZoneInfo('localtime')

# best to always make it timezone aware
dt1 = dtx.datetime(2019, 11, 12, 14, 30, 20, tzinfo=tz_utc)

# timezone conversion
dt2 = dt1.astimezone(tz_est)

print(dt1)
print(dt2)

print("daylight saving offset: ", dt2.dst())

dt3 = dtx.datetime(2019, 7, 12, 14, 30, 20, tzinfo=tz_utc)
dt4 = dt3.astimezone(tz_est)

print(dt3)
print(dt4)

print("daylight saving offset: ", dt4.dst())

# This adjusts for daylight saving
dt5 = dt4.replace(month=12)
print(dt5)

# This also adjusts for daylight saving
dt6 = dt4 + dtx.timedelta(weeks=22, days=-1)
print(dt6)

Timezones and Timestamps

import datetime as dtx
from datetime import timezone
from zoneinfo import ZoneInfo
from time import time

# localtime, naive, which should be okay to use in most instances
now_x = dtx.datetime.now()

# utc, naive, and very dangerous to use, since it might be assumed to be localtime
# AVOID THIS AT ALL COST
now_y = dtx.datetime.utcnow()

# hour offset is correct, but is not daylight saving aware
now1 = dtx.datetime.now(dtx.timezone(dtx.timedelta(hours=-5)))

# tz aware utc datetime. These two are equivalent
now2 = dtx.datetime.now(timezone.utc)
now2_x = dtx.datetime.now(ZoneInfo("UTC"))

# localtime, tz aware and daylight saving aware
now3 = dtx.datetime.now(ZoneInfo("localtime"))

# To get the UNIX UTC Epoch in millis, all of the above will work EXCEPT now_y will be incorrect
millis_a = round(now3.astimezone(tz=timezone.utc).timestamp() * 1000)

# Here to confirm that the calculation was correct
millis_b = round(time() * 1000)

# To restore a datetime from Epoch Millis
dtx.fromtimestamp(millis_a / 1000.0, tz=ZoneInfo("UTC"))

Time deltas

import datetime as dtx

dt1 = dtx.datetime.now()

# deltas can be negative
dt2 = dt1 + dtx.timedelta(hours=-4, minutes=-20, seconds=-10)

# order matters when calculation timedeltas
print((dt1 - dt2).total_seconds())
print((dt2 - dt1).total_seconds())

delta = dt1 - dt2

# total seconds delta is the default
print("total seconds diff: ", delta.total_seconds())

# but other units can be calculated by dividing the delta by 1 of that unit
print("total hours diff: ", delta / dtx.timedelta(hours=1))
print("total days diff: ", delta / dtx.timedelta(days=1))
print("total minutes diff: ", delta / dtx.timedelta(minutes=1))

print("hours in a week", dtx.timedelta(weeks=1) / dtx.timedelta(hours=1))

# deltas can be fractional
dt3 = dt1 + dtx.timedelta(hours=3.5)

print(dt1)
print(dt3)