1

Consider this code:

class Bar(object): pass

class Foo(object):
    def bar(self): return Bar()

f = Foo()
def Bar(): pass
print(f.bar())

It prints None. But the poor guy bar didn't expect Bar to be what it became afterward!

My question is, what is the "best" (most elegant, most efficient, most Pythonic, whatever) code I can write inside of Foo (hopefully without polluting outside scopes) that will make sure bar can reference the Bar that was defined at the time of its declaration rather than invocation?

(It's not that I can't come up with any solutions, but rather it's that I don't know what the proper solution is.)

user541686
  • 205,094
  • 128
  • 528
  • 886
  • Quick thought will be to declare your `Bar` class within your `Foo`, then your `Bar` will not be polluted by outer scope – Anzel Oct 29 '15 at 09:42
  • @Anzel: Assume `Bar` is already declared, not something I'm declaring. (I realize I can make a class variable that will refer to it too, but I don't know if that's the best solution.) – user541686 Oct 29 '15 at 09:42
  • OK I'm with you, I'd love to see one clean solution too, given that the dynamic nature... – Anzel Oct 29 '15 at 09:45
  • 5
    In what way is this really a practical issue? This example only behaves the way it does because you're replacing `Bar` **in the scope** of `Foo`. In practice, writing a Python module/script/file, this will never happen. I.e., if you do `import Foo` in another module, it doesn't matter how many `Bar` identifiers may be in that module, `Foo` will always refer to the `Bar` in its own module. If you're retroactively changing `Bar` in your own file which you have full control over, that's your problem. Simply don't. – deceze Oct 29 '15 at 10:00
  • 1
    Follow PEP8: functions have lowercase_with_underscore_names: `def bar(): pass`. Program fixed. – Bakuriu Oct 29 '15 at 11:16

2 Answers2

2

To "statically bind" a name at the time of function/method creation, you could use a default argument:

class Bar(object): 
    pass

class Foo(object):

    def bar(self, Bar=Bar): 
        return Bar()

Per this famous Python "gotcha", default argument values are only evaluated once.

It isn't terribly useful in this case, as it's your own silly fault for naming a function the same as a class (and the ensuing error gives you useful information); binding the name only makes it more difficult to e.g. mock out the class later on. However, it can be useful to solve issues with late binding in nested functions, per this question.


Per ShadowRanger's comment, if you're using Python 3.x you can add *, to the list of arguments (bar(self, *, Bar=Bar)) to prevent the caller clobbering the default accidentally by passing too many positional arguments; any additional arguments will raise a TypeError.

Community
  • 1
  • 1
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • 2
    In Python 3, this workaround can be made slightly more robust, by making the "statically bound" arguments keyword only, so people don't accidentally replace them by calling with too many positional arguments for the "real" function prototype. You just slurp unnamed positional varargs: `def bar(self, *, Bar=Bar):` – ShadowRanger Oct 29 '15 at 10:57
  • "If you'd prefer a `TypeError`" - Passing too many args already gives you a `TypeError`. Did you mean "If you'd prefer to customize the `TypeError`" or something? `Foo().bar(1)` -> `TypeError: bar() takes 1 positional argument but 2 were given`. The `*args` approach can also work on Py2 unlike the plain `*`, but as you note, it means you have to perform the manual check as described where `*` is Py3 only but does the work for you. – ShadowRanger Oct 29 '15 at 11:15
  • 1
    @ShadowRanger nope, just misunderstood what the bare `*` did! Thank you; I tend to write 2.x- and 3.x-compatible code, so that was new on me. – jonrsharpe Oct 29 '15 at 11:19
  • Ah. Probably my fault for describing it as "slurping unnamed positional varargs"; syntactically that's how I think of it, but because they're unnamed, semantically it's different from the named case; it won't actually slurp without a name to assign to. – ShadowRanger Oct 29 '15 at 11:23
  • @ShadowRanger yes, it doesn't so much slurp as choke on them! – jonrsharpe Oct 29 '15 at 11:24
0

Answering my own question, but the answer is to put everything in a closure:

def static_define(locals, super, tuple):  # All arguments are bound STATICALLY
    # Anything defined here will be exported externally
    class Foo(tuple):
        def __new__(cls, *args):
            return super(Foo, cls).__new__(cls, args)
    return locals()

# The following line "exports" all the returned locals into the current global namespace
(lambda f, bi: (lambda g: g.pop(f.__name__) and bi.None or g.update((lambda args: (lambda l: bi.reduce(lambda l, v: l.pop(v, bi.None) and bi.None or l, args, l))(f(*bi.map(lambda v: bi.eval(v, g), args))))(bi.__import__('inspect').getargspec(f).args)))(f.__globals__))(static_define, __builtins__)

super = None  # tamper!
print Foo()   # unaffected by tampering
user541686
  • 205,094
  • 128
  • 528
  • 886