1

Sometimes when coding, I want "special sorts of strings" and "special sorts of integers" for documentation.

For example you might have.

def make_url(name:str) -> URL:

where URL is really a string. In some languages like C you can use a typedef for this, and in python you could do something like.

URL = str

Is there a correct way to do this? You can do stuff in a very programmatic way, and have:

class URL(str):
    pass

or even

class URL:
    def __init__(self, url):
        self.url

But both of these feel excessive such that for a lot of use cases they aren't really worth the overhead.

Att Righ
  • 1,439
  • 1
  • 16
  • 29
  • 1
    Do you mean an [alias](https://docs.python.org/3/library/typing.html?highlight=typevar#type-aliases)? You may just write `URL = str`, and use `URL` as a type. Or do you mean something like a protocol? – MrBean Bremen Jul 05 '21 at 11:55
  • "in python you could do something like `URL = str`"—Well does that work? If you already know what to do in Python, what is your question? – khelwood Jul 05 '21 at 12:08
  • @khelwood I guess I'm interested if there's an accepted approach. My understanding is that using `URL = str` doesn't exactly make another type. It might be convenient if, say, the type system allowed you to assign URL a url from a string, but required you to nnotate the arguments of a function that took a URL. – Att Righ Jul 05 '21 at 13:23
  • Might be relevant: [Type hints: Is it a bad practice to alias primitive data types?](https://stackoverflow.com/questions/52504347/type-hints-is-it-a-bad-practice-to-alias-primitive-data-types) – MisterMiyagi Jul 11 '21 at 20:09

1 Answers1

1

You can use the NewType helper function to create new types. Here's a small example:

from typing import NewType

UserId = NewType('UserId', int)
some_id = UserId(524313)

def foo(a: UserId):
    pass

def bar(a: int):
    pass

foo(some_id)  # OK
foo(42)  # error: Argument 1 to "foo" has incompatible type "int"; expected "UserId"
bar(some_id)  # OK

Pay attention to some points:

The static type checker will treat the new type as if it were a subclass of the original type. This is useful in helping catch logical errors [...]

Note that these checks are enforced only by the static type checker. At runtime, the statement Derived = NewType('Derived', Base) will make Derived a function that immediately returns whatever parameter you pass it. That means the expression Derived(some_value) does not create a new class or introduce any overhead beyond that of a regular function call.

alex_noname
  • 26,459
  • 5
  • 69
  • 86
  • Aha, that seems to be exactly what I was after thanks. I had just found this while following up MrBean's suggestion of aliases and this seems like it. – Att Righ Jul 05 '21 at 13:25
  • There seems to be [a new way of doing this in python 3.10](https://docs.python.org/3.10/whatsnew/3.10.html#pep-613-typealias) to give a hint to typing systems. For example, `Alias: TypeAlias = List[str]` – Att Righ Jul 14 '21 at 14:13
  • I think that `Alias` won't automatically help you catch errors though. It's helpful for readability, but unlike with NewType if you just say `URL: TypeAlias = str` and then try to use a non-url string it'll pass MyPy just fine. – Josiah Jul 16 '21 at 20:09