Issue
I'm trying to backport my Python3 package to Python2. I used pasteurize
and all works fine. I noticed that it imported a few stuff from builtins
including str
, int
and super
. I was wondering if importing everything from builtins
is safe. Yes, I know that in principle asterisk import is considered bad practice because it clutters the current namespace, doesn't make it clear what's imported and override names you don't intend to. But when it comes to builtins
in particular isn't it true that all of them are already present as names, are safe to import and shouldn't break anything?
Also, if using super
from builtins
in Python2 code is it safe to call it as Python3's super
with no arguments? Are there edge-cases where it may break with Python2?
from builtins import * # is this frowned upon?
from future import standard_library
standard_library.install_aliases()
class Foo(object):
def __init__(self):
super().__init__() # is this always safe in python 2?
Edit 1: Just to clarify, for Python2 builtins
comes from future
and is not a built-in module as in Python3.
Edit 2: Some people have suggested that calling super
with no arguments never works and that importing from builtins
makes no difference. Obviously this is wrong.
from __future__ import print_function
class Foo(object):
def __init__(self, x):
self.x = x
class Bar(object):
def __init__(self, x):
self.x = x + 1
class Baz(Bar, Foo):
def __init__(self, x):
super().__init__(x)
try:
b = Baz(1)
except TypeError as e:
# this will only happen in Python 2
print("Didn't work: {}; trying with builtins.super".format(str(e)))
from builtins import super
b = Baz(1) # this now works in Python 2.7 too
print(b.x)
Solution
This super is a reimplementation of the Python 3 super that works, but don't expect to be as efficient as the Python 3 super.
In Python 3 the compiler cheats, and whenever it sees the name super
referenced in a function, it automatically adds a cell variable called __class__
to the function. The super
function makes use of __class__
to make up for arguments not being passed to it. You can see this in action by doing something like:
class X:
def f(self):
super
return __class__
assert X().f() is X
assert X.f.__closure__[0].cell_contents is X
__class__
is defined once (when the function is first compiled)+ and so this lookup that super does is very quick.
newsuper
, on the other hand needs to drill down through the MRO (and any decorators) each and every time to figure out the type of self
and the type that the function was defined one. It seems good for back porting Python 3 (probably why it exists in future.builtins
). But that you should otherwise stick to the standard Python 2 super
, so people reading your code aren't surprised by this.
Implementation: (taken from https://github.com/rfk/magicsuper/blob/master/magicsuper/_super.py (as documented by future.builtins.newsuper
))
def super(typ=_SENTINEL, type_or_obj=_SENTINEL, framedepth=1):
'''Like builtin super(), but capable of magic.
This acts just like the builtin super() function, but if called
without any arguments it attempts to infer them at runtime.
'''
# Infer the correct call if used without arguments.
if typ is _SENTINEL:
# We'll need to do some frame hacking.
f = sys._getframe(framedepth)
try:
# Get the function's first positional argument.
type_or_obj = f.f_locals[f.f_code.co_varnames[0]]
except (IndexError,KeyError,):
raise RuntimeError('super() used in a function with no args')
try:
# Get the MRO so we can crawl it.
mro = type_or_obj.__mro__
except AttributeError:
try:
mro = type_or_obj.__class__.__mro__
except AttributeError:
raise RuntimeError('super() used with a non-newstyle class')
# A ``for...else`` block? Yes! It's odd, but useful.
# If unfamiliar with for...else, see:
#
# http://psung.blogspot.com/2007/12/for-else-in-python.html
for typ in mro:
# Find the class that owns the currently-executing method.
for meth in typ.__dict__.itervalues():
# Drill down through any wrappers to the underlying func.
# This handles e.g. classmethod() and staticmethod().
try:
while not isinstance(meth,FunctionType):
try:
meth = meth.__func__
except AttributeError:
meth = meth.__get__(type_or_obj)
except (AttributeError, TypeError):
continue
if meth.func_code is f.f_code:
break # Aha! Found you.
else:
continue # Not found! Move onto the next class in MRO.
break # Found! Break out of the search loop.
else:
raise RuntimeError('super() called outside a method')
# Dispatch to builtin super().
if type_or_obj is not _SENTINEL:
return _builtin_super(typ,type_or_obj)
return _builtin_super(typ)
Answered By - Dunes
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.