简体   繁体   中英

"from builtins import *" and super() in python2: bad practice?

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)

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)

I noticed that it imported a few stuff from builtins including str, int and super

you dont need to import things from bultins, since they are all imported by default.

is it safe to call it as Python3's super with no arguments?

no - super is not backward compatible, so you have to pass there the parameters:

Python 3:

class Foo(object):
    def __init__(self):
        super().__init__()

Python 2:

class Foo(object):
    def __init__(self):
        super(Foo, self).__init__()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM