Hacker News new | past | comments | ask | show | jobs | submit login
Python bug: Can assign [] = (), but not () = [] (python.org)
85 points by axit on May 27, 2015 | hide | past | favorite | 50 comments



I was very impressed with the collective thought process shown in the bug thread. It was quite constructive and I think demonstrative of why python has been such a successful project.


If it was PHP they'd just write it down and claim it's documented behaviour.


Not consistent with my recollection of PHP since version 5, where they have fixed tons of old inconsistencies from the parser to the API level.

Perhaps based on older versions of PHP or merely uninformed snark?


Past experience and humor, mostly.


It used to be quite bad, but it's getting better in a good rate. Of course they have to keep bad stuff around for backwards compatibility, but unlike say Java they wasn't afraid to break too if it was for something smaller.


This is a fun numpy quirk:

    >>> 'x'*3.5
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can't multiply sequence by non-int of type 'float'
    >>> import numpy as np 
    >>> print 'x'*np.float64(3.5) 
    xxx


As an occasional numpy contributor, I would call this a bug.


Yes, the expected output is xx›


  xxx›


xxxₓ


xxxv, actually.

(it makes sense if you know why Roman numerals look the way they do)


Good point - I found it after it caused a really weird bug in a library I use. I'll open an issue on github...


Turns out it's deprecated:

    >>> import warnings
    >>> warnings.simplefilter('always')
    >>> 'x'*np.float64(3.5)
    __main__:1: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future
    'xxx'


I get no such warning with 2.7.9 on linux.


It would be a warning from numpy, not Python. That comes from numpy/core/src/multiarray/conversion_utils.c and was added 2013-04-13 , which would be for NumPy 1.8, I believe.


I use R predominantly, so forgive the lack of adequate Python-ese.

Is this behavior because numpy overloads the multiplication operation with a string as string repetition and then implicitly casts the float64 down to an integer of 3? I'm curious why this behavior manifests. When I get a chance I'll test 'xyz'*np.float(3.5)


It's "normal" in python for this to work with integers:

    >>> "x" * 3
    xxx
The only weird part (and it's not that weird IMO) is that numpy's "float" can be implicitly coerced to integer.


Dave Beazley gave a talk showing this. See https://twitter.com/renfredxh/status/586653125970386945


This is mostly harmless, as most people in that thread already noted.


Using [] = to exhaust generators (as mentioned in the bug report) is actually pretty awesome in my mind.


>>> [a, b] = 2, 3

>>> (a, b) = 2, 3

>>> {a, b} = 2, 3

  File "<interactive input>", line 1
SyntaxError: can't assign to literal


That could be because sets aren't ordered (even though it looks odd in this context).


I thought that at first too, but even then the exception seems spurious. It would be more inline with existing Python behavior to evaluate it as undefined/indeterminate assignment order (though this would be pretty useless).

>>> a, b, c, d = {1, 2, 3, 'a'}

>>> a

'a'

Regardless, it shouldn't be surprising when writing insane code, that the language starts acting insane. Python as a whole is pretty decent when it comes to handling syntactic edge cases.


The tricky part is that the [a,b] and (a,b) in your original examples are not list/tuple literals, but a target_list according to Python's grammar [0], which can be optionally enclosed in parentheses or brackets. To be honest, I didn't know the latter were allowed, even though I've been using Python for years. IMHO, they should have just allowed parentheses. It's like the parentheses in function calls--not really a tuple, but syntactic grouping. Why have more that one way to do the same thing?

[0]: https://docs.python.org/3/reference/simple_stmts.html#assign...


This is what i see in javascript:

[] = (); VM105:2 Uncaught ReferenceError: Invalid left-hand side in assignment

() = []; VM114:2 Uncaught SyntaxError: Unexpected token )


I can also do this, which is harmful.

    >>> True = False
    >>> False = True


Only in Python 2, since that is a backward-compatibility feature. (“False” and “True” used to be variables containing 0 and 1 before Python had true booleans, many ages ago.) Python 3 makes “True” and “False” be constant values, like “1” or “2”, like they should be.


Only constant for certain values of "constant". ;)

(https://gist.github.com/Jach/1208215 works in Python 3 too.)


After “import ctypes”, all bets are off.


There are ways to mark memory pages as read-only. It's not even difficult.


Why would you do that? That code is working perfectly as intended, the user is allowed to do that


Are there any codes dependent on True and False being assignable? Don't those codes deserve to die?


Yes. There are codes written with backwards compatibility to versions of Python pre-2.3, which did not have True/False. They typically looked like this:

  try:
    True
  except NameError:
    True = 1==1
    False = not True
Such code would likely be at least 10 years old. Given the transition to Python 3, it seems the general answer is "yes, they deserve to die."


Couldn't that be kept compatible by allowing tautological assignments to True/False (True = (1==1), that sort of thing) but throwing an error on all other assignments?

Also, I don't see how that wouldn't be compatible with forbidding assignments to True/False. It'd never execute the except block.


Yes, that specific case is possible with some sort of AST rewriting. But it's only one of several ways to introduce a True into the module or local namespace. Another might be:

  from _compat import True, False, next, enumerate
This is allowed under Python 2, but not under Python 3. As you suggest, it could be made possible to allow an import like this so long as the actual imported value is indeed the True or False singleton. But it's a lot of work with little gain.

While on the other hand, even in Python 2 it was a SyntaxError to say "None = None" or to import None. Extending that check to include True and False is much easier, and consistent with existing use.


The second line doesn't do anything though, since you already reassigned True to False it's like assigning False to False.

You could write (False, True) = (True, False) though.


Python 3 removed this feature unfortunately.


Unfortunately? I'd love to know when changing false to mean true and vice-versa would make sense in any codebase.


>>> (False, True) = (True, False)

>>> 1 if False else 0

1

>>> bool(0) == False

False


Not entirely sure of your point but...

Python 3.4.0 (default, Apr 11 2014, 13:05:11)

[GCC 4.8.2] on linux

Type "help", "copyright", "credits" or "license" for more information.

>>> (True, False) = (False, True)

  File "<stdin>", line 1
SyntaxError: can't assign to keyword


But, like the parent said, in Python 3:

    >>> (False, True) = (True, False)
      File "<stdin>", line 1
    SyntaxError: can't assign to keyword


    In[2]: (True, False) = (False, True)
    In[3]: True
    Out[3]: False
    In[4]: 1 if False else 0
    Out[4]: 1
    In[5]: 'yes' if bool(0) == False else 'no'
    Out[5]: 'no'
    In[6]: bool(0) == False
    Out[6]: False
What bothers me here is that when the interpreter says "False", it clearly means the opposite of (the new) False. So by reassigning True and False, I've actually caused inconsistent behavior in python, not just really-confusingly-named behavior. Suddenly False sometimes means one thing and sometimes means something else.


There's no inconsistency, the "False" printed in your console is just the repr of the object, you can give that repr to your own object if you have nothing better to do with your life:

    >>> A()
    False
    >>> bool(A())
    True
    >>> type(A())
    <class '__main__.A'>
A repr is just a representation in development context, nothing more and nothing less, there is no requirement that it be sensible or of any use, though that's certainly recommended (contrary to __str__/__unicode__ which should be silly):

    >>> B()
    A gray parrot
The only "inconsistency" you've created is that False in the console's local namespace does not match __builtins__.False or the interpreter's internal False object. You can still access the former by the way:

    >>> False
    True
    >>> __builtins__.False
    False
unless you also override it:

    >>> __builtins__.False = True
    >>> __builtins__.False
    True
the interpreter still does not care though, you've just broken any Python code relying on the builtin (you can actually alter the "true" False object via ctypes)


Waaa! ... I just did that using 2.7 idle and it worked. This is new for me.

I got stuck for a while trying to back out. Finally figured the way to reverse is to use del(): del(True) del(False).


You could also do True = True == True. It's like Dorothy clicking her heels together three times.


> The starting point is recognizing that this has been around for very long time and is harmless.


() is not a tuple. (a,b) is a tuple or (a,) would also be a tuple.


It's an empty tuple.

stuff = ()

print(type(stuff))

<class 'tuple'>


....__class__ # Python 3 only


[deleted]


what? no no no.

a) did you even read the link b) what does your statement even mean? note that (a,b,c) = (1,2,3) works perfectly.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: