Python is DOOMED! Again!
rosuav at gmail.com
Thu Jan 22 16:11:40 CET 2015
On Fri, Jan 23, 2015 at 1:16 AM, Steven D'Aprano
<steve+comp.lang.python at pearwood.info> wrote:
> Mario Figueiredo wrote:
>> def handle_employees(emp: Union[Employee, Sequence[Employee]], raise:
>> Union[float, Sequence[float]]) -> Union[Employee, Sequence[Employee],
> 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,
"""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):
for e in emp:
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:
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:
(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
(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.
(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.
(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.
More information about the Python-list