What's the purpose of the + (pos) unary operator in Python?
Asked Answered
C

7

70

Generally speaking, what should the unary + do in Python?

I'm asking because, so far, I have never seen a situation like this:

+obj != obj

Where obj is a generic object implementing __pos__().

So I'm wondering: why do + and __pos__() exist? Can you provide a real-world example where the expression above evaluates to True?

Claraclarabella answered 29/5, 2013 at 16:14 Comment(4)
Possibly a useful answer here: #10748685Factious
Related (different language, same query): What does the unary plus operator do?Osteoid
mail.python.org/pipermail/python-list/2004-September/…Submariner
If anyone which knows C/C++ expects that ++variable increments the variable, he is in for a surprise. The best thing is that it is valid python code, so it will not throw an error.Wingover
V
31

I believe that Python operators where inspired by C, where the + operator was introduced for symmetry (and also some useful hacks, see comments).

In weakly typed languages such as PHP or Javascript, + tells the runtime to coerce the value of the variable into a number. For example, in Javascript:

   +"2" + 1
=> 3
   "2" + 1
=> '21'

Python is strongly typed, so strings don't work as numbers, and, as such, don't implement an unary plus operator.

It is certainly possible to implement an object for which +obj != obj :

>>> class Foo(object):
...     def __pos__(self):
...        return "bar"
... 
>>> +Foo()
'bar'
>>> obj = Foo()
>>> +"a"

As for an example for which it actually makes sense, check out the surreal numbers. They are a superset of the reals which includes infinitesimal values (+ epsilon, - epsilon), where epsilon is a positive value which is smaller than any other positive number, but greater than 0; and infinite ones (+ infinity, - infinity).

You could define epsilon = +0, and -epsilon = -0.

While 1/0 is still undefined, 1/epsilon = 1/+0 is +infinity, and 1/-epsilon = -infinity. It is nothing more than taking limits of 1/x as x aproaches 0 from the right (+) or from the left (-).

As 0 and +0 behave differently, it makes sense that 0 != +0.

Versify answered 29/5, 2013 at 16:26 Comment(3)
Actually, in C the unary + has a use beyond symmetry. It doesn't change the value, but its presence forces the implicit conversions that happen on arithmetic (which may change the type).Siderolite
That's interesting. Could you post an example?Versify
@vklj: See e.g. verious.com/qa/does-the-unary-operator-have-any-practical-use (which also shows unary + being used to turn an l-value into an r-value).Gen
S
52

Here's a "real-world" example from the decimal package:

>>> from decimal import Decimal
>>> obj = Decimal('3.1415926535897932384626433832795028841971')
>>> +obj != obj  # The __pos__ function rounds back to normal precision
True
>>> obj
Decimal('3.1415926535897932384626433832795028841971')
>>> +obj
Decimal('3.141592653589793238462643383')
Stipitate answered 29/5, 2013 at 16:29 Comment(5)
I'm not Dutch, but does this seem like the obvious right way to round back to normal precision to anyone who is?Matney
@kojiro: yes. The reason is that decimals are immutable and therefore can carry more digits than the current precision allows so to take the current context into account, you need an explicit operation that uses it and may return a new decimal e.g., 0 + obj, 1 * obj, obj ** 1, etc. btw, localcontext() above is unnecessary: +Decimal('3.1415926535897932384626433832795028841971') creates 2 decimals (1st corresponds to the given string, 2nd correspond to current precision (the result)).Theone
The standard specification for decimal arithmetic specifies plus and minus functions that exhibit this behavior. In addition to providing those functions as part of a context, Python maps their behavior to unary - and +. See speleotrove.com/decimal/daops.html#refplusminFlight
@J.F.Sebastian Thanks for pointing that out; I edited to remove the references to localcontextStipitate
This looks like a bug in the decimal package more than anything.Infatuated
C
37

In Python 3.3 and above, collections.Counter uses the + operator to remove non-positive counts.

>>> from collections import Counter
>>> fruits = Counter({'apples': 0, 'pears': 4, 'oranges': -89})
>>> fruits
Counter({'pears': 4, 'apples': 0, 'oranges': -89})
>>> +fruits
Counter({'pears': 4})

So if you have negative or zero counts in a Counter, you have a situation where +obj != obj.

>>> obj = Counter({'a': 0})
>>> +obj != obj
True
Chemosynthesis answered 16/9, 2013 at 0:41 Comment(1)
And similartly for -fruits(which gives Counter({'oranges': 89})).Friulian
V
31

I believe that Python operators where inspired by C, where the + operator was introduced for symmetry (and also some useful hacks, see comments).

In weakly typed languages such as PHP or Javascript, + tells the runtime to coerce the value of the variable into a number. For example, in Javascript:

   +"2" + 1
=> 3
   "2" + 1
=> '21'

Python is strongly typed, so strings don't work as numbers, and, as such, don't implement an unary plus operator.

It is certainly possible to implement an object for which +obj != obj :

>>> class Foo(object):
...     def __pos__(self):
...        return "bar"
... 
>>> +Foo()
'bar'
>>> obj = Foo()
>>> +"a"

As for an example for which it actually makes sense, check out the surreal numbers. They are a superset of the reals which includes infinitesimal values (+ epsilon, - epsilon), where epsilon is a positive value which is smaller than any other positive number, but greater than 0; and infinite ones (+ infinity, - infinity).

You could define epsilon = +0, and -epsilon = -0.

While 1/0 is still undefined, 1/epsilon = 1/+0 is +infinity, and 1/-epsilon = -infinity. It is nothing more than taking limits of 1/x as x aproaches 0 from the right (+) or from the left (-).

As 0 and +0 behave differently, it makes sense that 0 != +0.

Versify answered 29/5, 2013 at 16:26 Comment(3)
Actually, in C the unary + has a use beyond symmetry. It doesn't change the value, but its presence forces the implicit conversions that happen on arithmetic (which may change the type).Siderolite
That's interesting. Could you post an example?Versify
@vklj: See e.g. verious.com/qa/does-the-unary-operator-have-any-practical-use (which also shows unary + being used to turn an l-value into an r-value).Gen
A
15

A lot of examples here look more like bugs. This one is actually a feature, though:

The + operator implies a copy.

This is extremely useful when writing generic code for scalars and arrays.

For example:

def f(x, y):
    z = +x
    z += y
    return z

This function works on both scalars and NumPy arrays without making extra copies and without changing the type of the object and without requiring any external dependencies!

If you used numpy.positive or something like that, you would introduce a NumPy dependency, and you would force numbers to NumPy types, which can be undesired by the caller.

If you did z = x + y, your result would no longer necessarily be the same type as x. In many cases that's fine, but when it's not, it's not an option.

If you did z = --x, you would create an unnecessary copy, which is slow.

If you did z = 1 * x, you'd perform an unnecessary multiplication, which is also slow.

If you did copy.copy... I guess that'd work, but it's pretty cumbersome.

Unary + is a really great option for this.

Antisepsis answered 15/9, 2019 at 8:57 Comment(7)
Maybe to you it implies a copy. It's not at all obvious that is should, and it certainly isn't supported by the things people most often try to copy: lists, dicts, sets, etc.Ambros
@chepner: I've never seen something like +dict work in any language for you to dispute whether it implies a copy or not... it seems like you might be confused by what "implies" means?Antisepsis
You are the one claiming that + implies a copy: on what basis?Ambros
@chepner: In a sane world + would do the same thing as - except that its return value has the opposite sign... it would be rather insane for y = +(+x); z = -(-x); x += 1; assert(y == z) not to hold true. Which... shouldn't exactly be shocking news. Surely you're aware that Python (and C++, etc.) as well numerous third-party libraries have been designed so that this does hold true. If that translates into "certainly not supported" by reality, I... don't know what to offer you.Antisepsis
There is a big difference between - and +: - always changes the sign of its argument; + would be a no-op on a value that is already positive. I have no reason to think that +(+x) should make even one, let alone two, copies of x rather than simply returning x itself (which is exactly what int.__pos__ does).Ambros
@chepner: Yes, it changes its sign. That says nothing about copying. int is immutable and wouldn't violate that in inequality. And it's patently false that you have "no reason" to believe +(+x) would make a copy. It literally does so for mutable cases and you're well-aware of this. That itself gives you reasons to believe this. The fact that you're ignoring reality doesn't change it I'm not interested in a pointless argument so this will be my last comment.Antisepsis
Please provide an example of an existing type that makes use of this idiom.Ambros
C
8

For symmetry, because unary minus is an operator, unary plus must be too. In most arithmetic situations, it doesn't do anything, but keep in mind that users can define arbitrary classes and use these operators for anything they want, even if it isn't strictly algebraic.

I know it's an old thread, but I wanted to extend the existing answers to provide a broader set of examples:

  • + could assert for positivity and throw exception if it's not - very useful to detect corner cases.
  • The object may be multivalued (think ±sqrt(z) as a single object -- for solving quadratic equations, for multibranched analytical functions, anything where you can "collapse" a twovalued function into one branch with a sign. This includes the ±0 case mentioned by vlopez.
  • If you do lazy evaluation, this may create a function object that adds something to whatever it is applied to something else. For instance, if you are parsing arithmetics incrementally.
  • As an identity function to pass as an argument to some functional.
  • For algebraic structures where sign accumulates -- ladder operators and such. Sure, it could be done with other functions, but one could conceivably see something like y=+++---+++x. Even more, they don't have to commute. This constructs a free group of plus and minuses which could be useful. Even in formal grammar implementations.
  • Wild usage: it could "mark" a step in the calculation as "active" in some sense. reap/sow system -- every plus remembers the value and at the end, you can gather the collected intermediates... because why not?

That, plus all the typecasting reasons mentioned by others.

And after all... it's nice to have one more operator in case you need it.

Claim answered 18/2, 2016 at 13:46 Comment(0)
F
5

__pos__() exists in Python to give programmers similar possibilities as in C++ language — to overload operators, in this case the unary operator +.

(Overloading operators means give them a different meaning for different objects, e. g. binary + behaves differently for numbers and for strings — numbers are added while strings are concatenated.)

Objects may implement (beside others) these emulating numeric types functions (methods):

    __pos__(self)             # called for unary +
    __neg__(self)             # called for unary -
    __invert__(self)          # called for unary ~

So +object means the same as object.__pos__() — they are interchangeable.

However, +object is more easy on the eye.

Creator of a particular object has free hands to implement these functions as he wants — as other people showed in their real world's examples.

And my contribution — as a joke: ++i != +i in C/C++.

Friulian answered 21/11, 2016 at 20:7 Comment(1)
__pos__ exists because + does; this doesn't' explain why + exists in the first place.Ambros
A
1

Unary + is actually the fastest way to see if a value is numeric or not (and raise an exception if it isn't)! It's a single instruction in bytecode, where something like isinstance(i, int) actually looks up and calls the isinstance function!

Aculeate answered 20/12, 2021 at 6:27 Comment(5)
Do you have any source for this?Nearsighted
godbolt.org/z/ExaGf56qMAculeate
Wow, this is enlightening! I also tested it using the timeit module and it's consistent with your answer.Nearsighted
Counter is not numeric but implements +Claraclarabella
pro: really fast int num check; con: really slow exception handling. lolNed

© 2022 - 2024 — McMap. All rights reserved.