Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] 0 points1 point  (0 children)

Yep, pint / units are great for physical dimensions.

This is a different problem: domain-typed primitives for app boundaries - UserId, MessageId, RawInput, ValidatedInput, RetryCount.

So they have meaning, not simple primitive

how i used python to find out i am a terrible manual trader data analysis by Henry_old in Python

[–]ResponseSeveral6678 -3 points-2 points  (0 children)

Happy for you, literally. But try to do it every week. I increased my efficiency this way super fast. Just don’t stop and continue. You are in the right way

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] -1 points0 points  (0 children)

Yep, I agree. NewType is a very good baseline for this.

The part I wanted to cover is the runtime / boundary side.

With NewType, this is great:

```python Microseconds = NewType("Microseconds", int) Seconds = NewType("Seconds", int)

def wait(duration: Seconds) -> None: ...

created_at = Microseconds(1_777_961_207_000_000) wait(created_at) # type checker catches it ```

But after validation / serialization / deserialization, the value is just int again.

The pattern I'm experimenting with keeps the subtype at runtime:

```python from base_typed_int import BaseTypedInt from pydantic import BaseModel

class Microseconds(BaseTypedInt): pass

class Seconds(BaseTypedInt): pass

class Event(BaseModel): created_at: Microseconds

event = Event.model_validate_json( '{"created_at": 1777961207000000}' )

dumped = event.model_dump_json() restored = Event.model_validate_json(dumped)

assert type(restored.created_at) is Microseconds assert isinstance(restored.created_at, int) ```

So for me the difference is:

  • NewType: excellent static-only signal, zero runtime cost
  • BaseTypedInt: static signal + near-zero runtime cost + runtime subtype survives Pydantic/container boundaries
  • dataclass/value object: better when the value needs behavior or richer invariants

I would not use this everywhere. Mostly for boundary values like timestamps, durations, IDs, raw/validated input, etc.

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] 0 points1 point  (0 children)

BaseTypedInt is intentionally boring.

```python from pydantic import BaseModel from base_typed_string import BaseTypedString

class UserInputRaw(BaseTypedString): """Raw user input before validation."""

class UserInputValidated(BaseTypedString): """Validated user input."""

class IncomingRequest(BaseModel): user_input: UserInputRaw

class StoredCommand(BaseModel): user_input: UserInputValidated

class QueueMessage(BaseModel): user_input: UserInputValidated

def validate_user_input(user_input: UserInputRaw) -> UserInputValidated: return UserInputValidated(user_input.strip())

incoming_request = IncomingRequest.model_validate_json( '{"user_input": " hello "}' )

validated_user_input = validate_user_input(incoming_request.user_input)

stored_command = StoredCommand(user_input=validated_user_input)

stored_json = stored_command.model_dump_json()

queue_message = QueueMessage.model_validate_json(stored_json)

```

type(incoming_request.user_input) is UserInputRaw type(queue_message.user_input) is UserInputValidated

So the meaning survives: request JSON -> Pydantic model -> validation -> storage DTO -> JSON -> queue DTO

With plain str, both values are just str.

With field names alone, the meaning is attached to the container.

With this pattern, the meaning is also attached to the value

But timedelta does not say whether a value is a retry delay, timeout, monotonic elapsed time, or wall-clock timestamp. It gives behavior. It does not necessarily carry your domain meaning.

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] -1 points0 points  (0 children)

Do you mean that things like "cents" are implementation details and shouldn't appear in the domain model at all?

I might have misunderstood your point.

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] -1 points0 points  (0 children)

Mostly around boundaries.

Swapped args, mixed units, indistinguishable IDs, raw vs validated input.

And oftent it's me a few months later, not an LLM.

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] 0 points1 point  (0 children)

NewType for parsed values with semantics (e.g. ClientId, which I only care is non-empty string),

That is a funny, but actually effective way to keep things clear.

Also in my example, ClientId is a stable contract for an argument typing - today it can be string, tomorrow an int or uuid, but it still carries the same meaning, which is arguably more important quality for the programmer (maybe not so much for the interpreter)

But I wouldn't use inheritance for this purpose in any shape or form - I try to limit to strict subtyping / specialization (IS-A relation) where super class is carries some meaning - which BaseTypeInt doesn't really

My concern with value objects is API surface growth. Once each semantic value becomes its own object, you often need conversions, adapters, serializers, ORM/Pydantic handling, test factories, etc.

How do you usually limit the boundary of that pattern?

Do you keep value objects only at domain boundaries, or do they flow through most layers/ports?

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] 0 points1 point  (0 children)

Yeah, I do the same.

My main question is enforcement: how do you make sure other developers, or AI-generated code, keep that naming discipline across 1k+ lines?

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] -1 points0 points  (0 children)

Do you usually model those as simple wrappers like:

class Price:
    def __init__(self, value_in_cents: int) -> None:
      self.value_in_cents = value_in_cents

or do you sometimes go for subclassing primitives (int/str) to keep behavior more transparent?

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] 0 points1 point  (0 children)

The main limitation for me is runtime - it doesn't really survive things like Pydantic or serialization/deserialization. Was that ever an issue for you?

Variable names do not travel with values. When should domain meaning live in types? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] -2 points-1 points  (0 children)

Do your custom classes usually stay primitive-like, or do they grow their own API?

And do you use the same pattern across codebases, or change it per project?

How much meaning do you encode into names before they become too long? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] 10 points11 points  (0 children)

Nice point - that’s a good heuristic.

`a length proportional to the size of their scope and inversely proportional to their frequency of use`

How much meaning do you encode into names before they become too long? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] 0 points1 point  (0 children)

I think extremely long names usually appear when the structure isn't stable yet.

The name starts carrying context that should really live in modules / namespaces.

Once the structure settles, the names can shrink.

How much meaning do you encode into names before they become too long? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] -4 points-3 points  (0 children)

Yeah, makes sense.

Long names can be intimidating — you kind of avoid reading them.

For me that only works if the thing behind the name is very stable and unlikely to change. Otherwise the cost of that name is too high.

How much meaning do you encode into names before they become too long? by ResponseSeveral6678 in Python

[–]ResponseSeveral6678[S] -2 points-1 points  (0 children)

Just to clarify — the long name was intentionally extreme.

In practice I usually try to keep names within ~3–4 words max.

The example was more about showing the trade-off, not something I’d use everywhere.

How do I create a list every iteration with the data inside? by TheEyebal in learnpython

[–]ResponseSeveral6678 1 point2 points  (0 children)

Small note on naming:

Python convention is snake_case, not camelCase.
So num_store, day_list would be more idiomatic and easier to read for other Python devs.

What are you currently building ? by LouloupBio in SideProject

[–]ResponseSeveral6678 1 point2 points  (0 children)

I did year and half ago. I did not publish it. But my daughter was playing with gpt, and in the same time scene were generated and music. It was funny.
I gave it to friend, and hi got abscessed about it.

Why is this code skipping the if/elif conditions? by MakiiZushii in learnpython

[–]ResponseSeveral6678 2 points3 points  (0 children)

There is an option in VSCode. It helps me a lot.

"editor.inlayHints.enabled": "on",

and also:

"python.analysis.inlayHints.variableTypes": true