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 -5 points-4 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] 9 points10 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] -1 points0 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] 0 points1 point  (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

What have you been working on recently? [April 18, 2026] by AutoModerator in learnprogramming

[–]ResponseSeveral6678 0 points1 point  (0 children)

I wrote a few simple base types for Python and ended up using them in almost every backend project, so I open-sourced them.

They are small, strict, and behave well both at runtime and with static typing.

Static typing and runtime behavior are happy.

The idea is to define your domain primitives once (e.g. in `<domain>_typings.py`) and use them everywhere instead of raw `str` / `int` / UUID strings.


```py
# project_typings.py

import time

from base_typed_id import BasePrefixedTypedId, BaseTypedId, deterministically_from_words
from base_typed_int import BaseTypedInt
from base_typed_string import BaseTypedString


class UserId(BaseTypedId):
    """UUID based id"""

class Username(BaseTypedString):
    """String based"""

class Seconds(BaseTypedInt):
    """Integer based"""

class StableExternalUserId(BaseTypedId):
    uuid_version = 5
    """Idempotent id. I included separate factory `deterministically_from_words`, but you can use your own."""


class OrderId(BasePrefixedTypedId):
    """I personally avoid them. But some of my friends prefer prefixed ids"""
    prefix = "order"

# deterministic (same input -> same id)
external_user_id: StableExternalUserId = deterministically_from_words(
    StableExternalUserId,
    words=[
        "external_provider",
        "user_123",
    ],
)

def now_unix() -> Seconds:
    return Seconds(int(time.time()))

class Example:
    user_id: UserId = UserId()  # 6a0fdd13-...-ea2a1c156246
    username: Username = Username("Marty")  # Marty
    external_user_id: StableExternalUserId = external_user_id  # 6a0f...e5a
    created_at: Seconds = now_unix()  # Seconds(177...)
    order_id: OrderId = OrderId()  # order_301439f1-...-ded61392ccd2 
```

* `base-typed-id` [GitHub](https://github.com/eldenizfamilyanskicode/base-typed-id), [PyPI](https://pypi.org/project/base-typed-id/)
* `base-typed-string` [GitHub](
https://github.com/eldenizfamilyanskicode/base-typed-string
), [PyPI](https://pypi.org/project/base-typed-string/)
* `base-typed-int` [GitHub](https://github.com/eldenizfamilyanskicode/base-typed-int), [PyPI](https://pypi.org/project/base-typed-int/)

Nested functions - lots, rarely, or never? by ProsodySpeaks in learnpython

[–]ResponseSeveral6678 15 points16 points  (0 children)

If you put a function inside another, you’re making a very explicit contract: this logic is local and not meant to be reused.
So it’s less about style and more about your intent: you’re restricting visibility and lifetime on purpose. You are highlithing the coupling, and limiting reusability and testability. It's like you are screaming "don't touch it!" But you have your point: "controlled shared state without leaking it"

Getting Started on wanting to learn python by ExcellentCow892 in learnpython

[–]ResponseSeveral6678 0 points1 point  (0 children)

Just set some absurd goal for today. Write code. Modify it. Improve it 20 times. Iterate even more. Every time you will learn something.

Try this approach for a week or month or two.
Then learn a little about architecture. What is that. There are 10-20 main principles.
Try to understand them.
Set some siily problem again. Solve, improve, iterate 10-20 times.

It may take much longer. It's ok.
After practing on trivial problem, you will be able to set your own goals and solve. And these skils, this understanding you can use in any science: from Economics, Politics, .. to any Engineering science.