[Edu-sig] source code from SA:10648

kirby urner kirby.urner at gmail.com
Tue Jul 20 07:00:02 CEST 2010


On Mon, Jul 19, 2010 at 6:55 PM, Tim Peters <tim.peters at gmail.com> wrote:
> [kirby urner]
>> ... here's another basic
>> question.  Is there a way, after importing from __future__,
>> to revert to the "old ways" later on in the same script?
>> ...
>> I'm not saying this would ever be a good idea, just
>> wondering about possibilities...
>
> In general it's not possible.  At least CPython compiles an entire
> file into bytecode before anything is executed.  That's why __future__
> imports have to be the first non-trivial statements in a file:  they
> can change just about anything, including the bytecode that gets
> generated.
>
> For example, here's a little program:
>
> def f():
>    print x / y
>
> import dis
> dis.dis(f)
>
> That defines a tiny function, then displays the bytecode generated:
>
>  2           0 LOAD_GLOBAL              0 (x)
>              3 LOAD_GLOBAL              1 (y)
>              6 BINARY_DIVIDE
>              7 PRINT_ITEM
>              8 PRINT_NEWLINE
>              9 LOAD_CONST               0 (None)
>             12 RETURN_VALUE
>
> It's not necessary to know what any of those mean, although they
> should be pretty obvious ;-)  If you change the program by putting:
>
> from __future__ import division
>
> at the top, the output changes:
>
>  4           0 LOAD_GLOBAL              0 (x)
>              3 LOAD_GLOBAL              1 (y)
>              6 BINARY_TRUE_DIVIDE
>              7 PRINT_ITEM
>              8 PRINT_NEWLINE
>              9 LOAD_CONST               0 (None)
>             12 RETURN_VALUE
>
> Note that the third operation changed, from BINARY_DIVIDE to BINARY_TRUE_DIVIDE.
>


Thank you Tim, that's uber-clear and enlightening.

I notice that in IDLE, even a shell restart (menu option) does not
reset division back to the old way.

IDLE 2.6.5
>>> 1/4
0
>>> from __future__ import division
>>> 1/4
0.25
>>> ==== RESTART ====
>>> 1/4
0.25

I also recall how special name __div__ will be triggered by default
in Python 2.6 until division is imported from __future__, then
__truediv__ gets triggered instead:

>>> class Foo (object):
	def __truediv__(self, other):
		print "__truediv__ triggered"
	def __div__(self, other):
		print "__div__ triggered"

>>> o = Foo()
>>> b = Foo()
>>> o/b
__div__ triggered

>>> from __future__ import division
>>> o/b
__truediv__ triggered

If all you've done is define __div__ for your object, that'll work
by default, until you import division, then unless you have
__truediv__ defined, you'll be getting those error messages:

>>> class Foo (object):
	def __div__(self, other):
		print "__div__ triggered"

		
>>> a = Foo()
>>> a/3
__div__ triggered

>>> from __future__ import division
>>> a/3

Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    a/3
TypeError: unsupported operand type(s) for /: 'Foo' and 'int'
>>> a/a

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    a/a
TypeError: unsupported operand type(s) for /: 'Foo' and 'Foo'

> This is all done before anything in the file is executed, so after
> execution begins it's too late to change any of it:  the opcode will
> remain whatever was generated at the start (BINARY_DIVIDE or
> BINARY_TRUE_DIVIDE) no matter what you do while the program is
> running.  After all, the instant after you import the future, it
> becomes the past, and nobody can change the past ;-)
>

I'm a little surprised that resetting the IDLE shell doesn't reset
division "back to factory".  It does disappear from the namespace:

>>> dir()
['Foo', '__builtins__', '__doc__', '__name__', '__package__', 'a', 'division']

>>> ======== RESTART =======

>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']

>>> division

Traceback (most recent call last):
  File "<pyshell#15>", line 1, in <module>
    division
NameError: name 'division' is not defined

>>> 1/4
0.25

Maybe it shouldn't be allowed to disappear from the namespace (del division
raises an exception) as long as its effects are enforced.  That way a program
could test for it.  But then I supposed there's another way.

Reading from PEP 238:

    The true and floor division APIs will look for the corresponding
    slots and call that; when that slot is NULL, they will raise an
    exception.  There is no fallback to the classic divide slot.

    In Python 3.0, the classic division semantics will be removed; the
    classic division APIs will become synonymous with true division.

Testing in 3.1, I see __div__ is indeed gone as a special name for
integers.  Only __truediv__ ( and __floordiv__ and their =/ =//
semantics) have been retained.

__div__ may be in your code, but it's not a special name (nor is
it a mangled private function given the trailing __ prevents mangling).

Python 3.1.2 (r312:79149, Mar 21 2010, 00:41:52) [MSC v.1500 32 bit
(Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> class Foo (object):
	def __truediv__(self, other):
		print ("__truediv__ triggered")
	def __div__(self, other):
		print ("__div__ triggered")

		
>>> o = Foo()
>>> o/3
__truediv__ triggered

>>> dir(3)  # showing __div__ is all gone

['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__',
'__delattr__', '__divmod__', '__doc__', '__eq__', '__float__',
'__floor__', '__floordiv__', '__format__', '__ge__',
'__getattribute__', '__getnewargs__', '__gt__', '__hash__',
'__index__', '__init__', '__int__', '__invert__', '__le__',
'__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__',
'__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__',
'__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__',
'__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__',
'__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__',
'__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__',
'__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__',
'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']


>>> o .__div__(3)
__div__ triggered

What's in __future__ anyway?

>>> import __future__
>>> dir(__future__)
['CO_FUTURE_ABSOLUTE_IMPORT', 'CO_FUTURE_DIVISION',
'CO_FUTURE_PRINT_FUNCTION', 'CO_FUTURE_UNICODE_LITERALS',
'CO_FUTURE_WITH_STATEMENT', 'CO_GENERATOR_ALLOWED', 'CO_NESTED',
'_Feature', '__all__', '__builtins__', '__doc__', '__file__',
'__name__', '__package__', 'absolute_import', 'all_feature_names',
'division', 'generators', 'nested_scopes', 'print_function',
'unicode_literals', 'with_statement']

Kirby


More information about the Edu-sig mailing list