2

Suppose we want to add type hinting to a function that adds a bunch of integers together using a starred argument:

def add_integers(*integers):
   return sum(integers)

Is the correct practice to give the integer type, since that's what each individual argument will be?

def add_integers(*integers: int):
   return sum(integers)

...or to give the type of the resulting variable, which ends up being a sequence (tuple) of integers?

def add_integers(*integers: Sequence[int]):
   return sum(integers)
Georgy
  • 12,464
  • 7
  • 65
  • 73
MarcTheSpark
  • 473
  • 5
  • 14

1 Answers1

4

According to PEP 484, the correct hinting is the first one

Sylvaus
  • 844
  • 6
  • 13
  • If there are no type constraints/restrictions on the extra arguments, then `Any` or `Tuple[Any]` would be accurate. If there were type constraints/restrictions, then `Tuple[type1, type2, ...]` would be accurate. I wonder why does the "acceptable" type annotation in the PEP assume all extra positional arguments will be of the same type. Bizarre. – Venkatesh-Prasad Ranganath May 23 '20 at 01:23
  • 1
    @Venkatesh-PrasadRanganath think it is more because it's the only that can be used for hinting. Any or Tuple[any] would be a fair but it does not help since you do not hint anything. Tuple[type1, type2, ...] cannot be used since you want to be able to have an arbitrary number of parameters passed in making the hinting only valid when the number of parameters defined in the hinting is equal to the numbers of parameters passed – Sylvaus May 23 '20 at 02:03
  • `Any` or `Tuple[Any]` explicitly communicate "any object can be passed in any order" (as opposed to we don't know what are the type constraints on extra arguments). Similarly, `Tuple[type1, type2, ..., typen]` can explicitly communicate type constraints such as "up to first n extras have to be ints". So, I think type hints can help communicate requirements (about extra arguments) more rigorously and I think we can do better job of using them to this end. – Venkatesh-Prasad Ranganath May 24 '20 at 05:04
  • I agree with you about `Any` for standard parameters, I was talking about starred parameters. However, if you want to constraint or present the type of the parameters, positional parameters would be a better choice than starred parameters. Starred parameters are most often used to collect a list of same type elements (which fits the hinting method) or forward parameters (in this case I hope that type inference can be used for your IDE or checker to show you the expected type) – Sylvaus May 24 '20 at 14:44
  • Is "Starred parameters are most often used to collect a list of same type elements" an assumption or a statement? If the latter, please provide pointers to it. If the former, what is its basis? I think starred parameter was intended to collect extra arguments with no type restriction; kinda like varargs in C. So, assumptions about homogeneity of types of extra arguments kinda breaks this intent; this is my concern. – Venkatesh-Prasad Ranganath May 24 '20 at 18:34
  • It's a statement based on experience. The basis is that if you have positional arguments with different types, named positional arguments would help the user by giving more information on what is expected. However, if you will just pass the arguments to an internal functions, it is easier to just use *args, **kwargs to forward the parameters. I guess the usage of varargs you are describing is in the print functions but that's a bad example as you could hint all the argument passed as "Printable" (which is always the case in Python) – Sylvaus May 24 '20 at 19:47
  • If "Printable" is a type, then you are right. What if it isn't? :) I think printf is an exemplar use case for variadic arguments -- we really don't know how many args or their type. Also, I think we cannot build reliable software based on "observations/assumptions based on experience"; I say assumption as it may not be what others have observed/assumed. Instead, we need statements that captures the intent of the designer(s) of the language and can serve as a reference. And, there is none in this context. This is my concern. BTW, I am not challenging your answer, just the state of affairs. – Venkatesh-Prasad Ranganath May 25 '20 at 00:25
  • Then you create the type Printable to show the minimal amount of requirements required to be a valid parameter (see concept in C++20). This way you do not over constrain the expected type and you have a perfect example how you can give the same "type" to all the parameters. I agree that you cannot build a reliable software **solely** based on "observations/assumptions based on experience" but it is still a big component of software development and the reason companies spend a fortune on Senior devs. – Sylvaus May 25 '20 at 12:30
  • So, we have to choose between creating a new type that every allowed argument type (including previously existing types) should implement vs a better hinting strategy. Which should we choose? :) Err, I meant "assmptions solely based on one's experience". I think senior devs (should) help check assumptions and use general and frequent observations that can be considered/codified as patterns/norms. Even so, we are better off codifying such general and frequent observations, e.g., like in PEP and language/ilbrary definition. – Venkatesh-Prasad Ranganath May 25 '20 at 17:26
  • The arguments you pass must already implement the required methods otherwise your function would not work, I don't see your point there. Taking the example of printf, you could easily hint with the current way using union: `Union[int, float, str]`. Patterns/norms started by the observation of one person. I am not saying my observation is right but that it should be debated a bit before being left on the side. – Sylvaus May 25 '20 at 18:02
  • Arguments need not implement printable methods; function may know how to handle specified argument types. Yes, either union type or a tuple type (in case of specific types in specific order) is a better hinting strategy as opposed to saying all types have to be the same as the PEP says. Yes, we should debate patterns/norms and then codifying their common purposes. Assuming the acceptable type annotation in PEP was debated, why was it "acceptable"? Without knowing the answer to this question and contrary valid use cases, I find the acceptable recommendation in the PEP bizarre. – Venkatesh-Prasad Ranganath May 26 '20 at 00:07
  • The `Union[int, float, str]` can be used with the current recommendation, so you basically said that the recommendation is better than the recommendation . And again, if the position of the arguments is important and you are not simply forwarding, having named parameters is a lot betters than using `*args`. I mean position and not just the order. – Sylvaus May 26 '20 at 00:18
  • As the number of type parameters increase, the union solution collapses into the `Any` recommendation, and you would have arrived at my earlier recommendation :) My concern was "what if we want to hint types of some extra arguments"? Bundling such args as collection-based positional args with defaults is a solution. Then, what is the purpose of "extra" args? Also, can you give a concrete example of forwarding and how it matters in this context? – Venkatesh-Prasad Ranganath May 26 '20 at 19:29
  • 1) Then you agree with the PEP recommendation 2) For once, I am gonna ask you to present an example of when using the collection-based positional argument is preferable to simple positional and keyword arguments. 3) Inside decorators is the perfect example of forwarding. I mention it as an occurrence where I don't think hinting should be used since you don't know and do not care about the parameters passed in (I'm talking to the newly created function and not the decorator itself) – Sylvaus May 27 '20 at 00:11
  • 1) As I mentioned in my first comment, I agree with the PEP reco for homogenerous args, i.e., allowing any value leads to`Any` hint. My concern is dealing with batches of homogeneous args. 2) Say something similar to `itertools.repeat`: `repeat(strs, reps)` that returns a list in which `strs[i]` is repeated `max(reps[i], 1)` times in order. 3) Thanks for the forwarding example. I agree decorators benefit from extra arguments. – Venkatesh-Prasad Ranganath May 28 '20 at 01:12
  • 2) I do not see where the hinting of *args comes into play for itertools.repeat. Even if you were to pass the result of itertools.repeat to a function, you would pass the iterator and that does not involve *args. Could you tell me if I'm missing something ? Because if there are no useful/maintainable/no edge case usage of *args with heterogeneous parameters other than forwarding then the PEP recommendation shouldn't be bizarre – Sylvaus May 28 '20 at 01:21
  • We could realize (2) as `repeat(*args)` that requires a sequence of strings followed by ints and call it as `repeat(*strs, *reps)`. However, we cannot hint this type. Alternatively, we can use collection-based positional args and provide usable type hints. Does that help? BTW, I am fine with PEP reco if there is some design/normative doc that says extra args should be homogeneous. If extra args were intended only for forwarding, then why recommend homogeneous type hint for them? For these reasons, I think the acceptable reco is bizarre. – Venkatesh-Prasad Ranganath May 28 '20 at 02:35
  • This function repeat would be much better as repeat(strs: Iterator[str], reps: Iterator[str]), otherwise the internal implementation would be a mess (having to go through args to find the list of strings then extracting the list of ints). The current repeat takes an Iterator not a *args. So no, it does not help since it is still not an example where you would use heterogeneous parameters in *args. In the case of forwarding, I don't think you want hinting at all (see previously mentioned reasons). That's why I always separated this case. – Sylvaus May 28 '20 at 12:03