On Fri, Jan 23, 2015 at 1:16 AM, Steven D'Aprano <steve+comp.lang.pyt...@pearwood.info> wrote: > Mario Figueiredo wrote: > >> def handle_employees(emp: Union[Employee, Sequence[Employee]], raise: >> Union[float, Sequence[float]]) -> Union[Employee, Sequence[Employee], >> None]: > > Using > floats for money is Just Wrong and anyone who does so should have their > licence to program taken away.
But using a float to specify a percentage raise that an employee (or class of employees) gets would be reasonable. You could "handle" all your problem employees by giving them an across-the-board 3% raise to try to stop them from going on strike (they have a Union, as we can see there, so strikes are clearly possible). Not that that matters to the typing question. If it really is a monetary type, all you need to do is replace 'float' with 'Money' which would be a superclass to USD, AUD, GBP, and RepublicCredits. There's still the question of what it's doing. My best guess about this function is that it has three modes: 1) handle_employee(emp: Employee, pay_raise: float) Give one employee a percentage raise. (Note the different function name.) 2) handle_employees(emp: Sequence[Employee], pay_raise: float) Give all these employees the same percentage raise. 3) handle_employees(emp: Sequence[Employee], pay_raise: Sequence[float]) Equivalent to [handle_employee(e, r) for e, r in zip(emp, pay_raise)] I can't figure out any sane meaning for passing a single employee and a sequence of floats, and I think the one-and-one case should have a different name. That would mean that there's really only one Union involved, and the plural function would look like this: def handle_employees(emp: Sequence[Employee], pay_raise: Union[float, Sequence[float]]): """Atomically give many employees raises. If pay_raise is a float, gives each employee that raise; otherwise, it should be a sequence of the same length as emp. """ if isinstance(pay_raise, collections.Sequence): for e, r in zip(emp, pay_raise): handle_employee(e, r) else: for e in emp: handle_employee(e, pay_raise) But I still have no clue what the return value of either the one-and-one case or the multiple case should be. > (3) Have a shorter way to declare "Spam or Sequence (tuple?) of Spam". > > > def handle_employees( > emp: OneOrMore[Employee], > pay_raise: OneOrMore[int]) > -> OneOrMore[Employee] | None: > pass Yes, that would be a reasonable thing. It might even be possible to implement it on top of TypeVar. That said, though, APIs that accept "just one, or maybe more" have problems; for instance, you can't write a "pretty-print" function that can take either an arbitrary object, or a sequence of arbitrary objects. Fundamentally not possible. But when you can guarantee that the thing you're getting OneOrMore of is not itself iterable, it would be useful. >> Meanwhile there's quite a few more generics like the Sequence one above >> you may want to take a look at and try and remember. And that's just one >> factory (the generics support factory). You may also want to take a look >> at TypeVar and Callable for more syntactic hell. > > Exaggerate, much? Maybe. Callable does get pretty hairy; specifying the arguments and return values of a function is never pretty. Pike is no better: > typeof(write); (1) Result: function(array(string) | string, mixed ... : int) That's a function which takes either a single string or an array of strings, followed by any number of additional arguments, and returns an integer. > typeof(GTK2.setup_gtk); (2) Result: function(void | array(string) | string, void | int : array(string)) Optional arguments: one or more strings, and maybe an integer. Returns an array of strings. > typeof(rm); (3) Result: function(string : int) This one's pretty simple. It takes a string (path name), and returns an integer (success or failure flag). And deletes a file/directory. > typeof(asin); (4) Result: function(int | float : float) Could take an integer or a float, and returns a float. The PEP 484 equivalents would be, I think: write = Callable[[Union[str, Sequence[str]], AnyArgs], int] # I have no idea how to do optional args. rm = Callable[[str], int] asin = Callable[[Union[int, float]], float] It's not too bad when the function signature is really simple. And that's going to be the common case. But it is syntactically complicated to type-check a callback's arguments. Imagine, for instance, GUI toolkit callbacks; chances are you can stuff extra args into them (so they need a *args equivalent at the end), and they'll take a variety of different args. They will not be pretty... or else they'll be type-hinted simply as Callable, with nothing else. But ultimately, that's not a fault of the type hinting structure. It's a natural consequence of the complexity of callbacks. You can't get away from it. ChrisA -- https://mail.python.org/mailman/listinfo/python-list