⇤ home

a good class - part 4

note: this is part 4 in a series of posts about oop.


back at it.

this series is moving slow, because i went back to double check on things.

today, two things: more array stuff and enums.


first, here’s the update on the array table:

Code Range
B [0, 255]
H [0, 65535]
I [0, 4 BILLION]
L a 20-digit number

don’t use anything else.

now, why did the letters change?

because the numbers may vary. it’s your operating system that chooses.

python arrays only guarantee a minimal length.

you can check the actual length with this code:

import array

for t in array.typecodes:
    a = array.array(t, [])
    print(a, a.itemsize)

which will output something like:

array('b') 1
array('B') 1
/home/le/tmp/temp.py:121: DeprecationWarning: The 'u' type code is deprecated and will be removed in Python 3.16
  a = array.array(t, [])
array('u') 4
array('w') 4
array('h') 2
array('H') 2
array('i') 4
array('I') 4
array('l') 8
array('L') 8
array('q') 8
array('Q') 8
array('f') 4
array('d') 8

since it’s calling the underlying c infrastructure, i’ll stick with:

the other thing i wanted to talk about is how long is long.

long is really long.

if you incremented the value of a long 100 times per second (for example the timer of your game running at 100fps), it would take 600 million years to fill up.

it’s bonkers.

now, is it enough for a secret?

uuid4() in python outputs 16 bytes, so it needs two L slots.

can we try 8? after all, i still think the collision risk is low enough with 8.

if you do:

import secrets

s = secrets.token_bytes(8)
u5 = array("L", s)

as a password, it would take around 25 million years to guess (I is not enough, 6 days to guess). for an id, you could use random.randbytes(8).

good enough?

maybe. i don’t think that’s the issue.

the issue is that i don’t need a user_id. nor do i need to bulk process passwords.

in ecs, the index is the id.

what’s the id of the entity living in the third column of my database?

it’s 3. just 3.

(well 2 because of 0-indexing but you get what i mean.)

larger elements like passwords or ids exist only at the boundary level. so it’s fine to have them in some user_id_to_index = {} hashmap.

and this is the perfect segue to my point number 2, design patterns.


i found out that arrays are a good “canary in the coal mine”.

if you wrestle with arrays, you’re very close to shooting yourself in the foot.

for example, strings can only exist in two places:

(array(w) is for storing characters, and i rarely need that)

the first place, i already told you about. it’s fine if you have a list of document names, or a hash map with different localized text, just don’t put them in the hot path, please.

(i even think they’re a great way to signal we’re still on the cold path)

but we need to talk about enums.

enums are a fantastic way to say “this value means that”.

look how beautiful this is:

from enum import IntEnum
from array import array


class Entity(IntEnum):
    PLAYER = 0 # no auto!
    GHOUL = 1
    GHOST = 2 


health = array('B', [100, 40, 40])
print(health[Entity.GHOUL])

no, for real, health[Entity.GHOUL], what a pleasure. there’s something so zen about this code, “i will represent my ghoul by the values at index 1”, well here you go pal.

one important thing to note: no use of auto(), because it starts at 1, which is inconvenient for indexes.

(why? c-related stuff i guess. i didn’t find more info in the pep)

i don’t mind not using auto, explicit is better than implicit anyways, except maybe for flags.

so… let’s add a flag!

from enum import IntEnum, IntFlag, auto
from array import array


class Entity(IntEnum):
    PLAYER = 0
    GHOUL = 1
    GHOST = 2 

class Status(IntFlag):
    BURNING = auto()
    FROZEN = auto()


health = array('B', [100, 40, 40])
attack = array('B', [15, 8, 9])
status = array('B', [1, 3, 3])

damage_mult = 1
if status[Entity.GHOUL] == (Status.FROZEN | Status.BURNING):
    damage_mult = 2

health[Entity.GHOUL] -= damage_mult * attack[Entity.PLAYER]
print(health[Entity.GHOUL]) # 10

oh yeah sorry, an IntFlag. well, int or no int, it’s just for not having to call value at the end. i much prefer it.

so here we have it, a combat calculation where the player damages a ghoul, dealing extra damage if the ghoul has two status effects.

but i didn’t need to tell you that. it’s obvious just by looking at the code. and it’s a joy to come back to.


well, we finally have an answer to shakespeare’s age-old question:

what’s in a name?

a number.

next episode, we try to implement a sparse set while not losing it.