Local variables initialization

Discussion in 'Python' started by Michal Kwiatkowski, Feb 26, 2006.

  1. Hi!

    I'm building a class that most of methods have similar intro, something
    like this:

    def method(self):
    var_one = self.attr_one
    var_two = self.attr_two.another_attr

    empty_list = []

    # significant code goes here
    # ...

    It's done for clarity reasons, aliasing most used variables to shorted
    names and initializing some other common variables. I'm wondering if I
    can generalize it in any way. So far I couldn't come up with any
    solution. It smells like macros (which won't be implemented in Python, I
    know, I know ;), but maybe there is a way? I would like to code it that way:

    @init_local_variables
    def method(self):
    # significant code goes here
    # ...

    If only such a decorator exists...

    Thanks for any hints.

    mk
    --
    . o . >> http://joker.linuxstuff.pl <<
    . . o It's easier to get forgiveness for being wrong
    o o o than forgiveness for being right.
     
    Michal Kwiatkowski, Feb 26, 2006
    #1
    1. Advertising

  2. Michal Kwiatkowski <> wrote:
    ...
    > def method(self):
    > var_one = self.attr_one
    > var_two = self.attr_two.another_attr
    > empty_list = []
    > # significant code goes here

    ...
    > know, I know ;), but maybe there is a way? I would like to code it that way:
    >
    > @init_local_variables
    > def method(self):
    > # significant code goes here


    Such a decorator would have to do very substantial bytecode rewriting,
    to turn all references to the magic names within the body of the method
    into local-variable references (and a lot of other things besides).
    Check the difference...:

    >>> import dis
    >>> def f1():

    .... x = 23
    .... return x
    ....
    >>> dis.dis(f1)

    2 0 LOAD_CONST 1 (23)
    3 STORE_FAST 0 (x)
    3 6 LOAD_FAST 0 (x)
    9 RETURN_VALUE
    >>> def f2():

    .... return x
    ....
    >>> dis.dis(f2)

    2 0 LOAD_GLOBAL 0 (x)
    3 RETURN_VALUE

    Every access to 'x' within f1 is a LOAD_FAST, because the compiler knows
    that x is local; but within f2 it's a different opcode, LOAD_GLOBAL,
    because the compiler knows that x ISN'T local (it's not assigned to
    within the body) and therefore it guesses it must be local.

    The whole function object, as well as the code object it contains, must
    be rewritten extensively to add the 'magic' names among the local
    variables -- not being assigned-to, they're used in all ways as globals
    in the code obejct and function object that the decorator receives.

    I would suggest that the issue is hairy enough to apply one of the least
    understood points of the "Zen of Python", namely:
    "If the implementation is hard to explain, it's a bad idea."

    If I absolutely HAD to implement such a "micromacro" system, after
    pushing back for all I'm worth and presumably failing, I'd punt and go
    for SOURCE manipulation instead (which does in turn give heavy
    constraints -- no bytecode-only distribution). For example, here's a
    WAY over-fragile example, intended STRICTLY as such:

    import inspect

    def manip(f):
    s = inspect.getsourcelines(f)[0]
    delta = len(s[0])-len(s[0].lstrip())
    del s[0]
    s[:] = [x[delta:] for x in s]
    delta = len(s[1])-len(s[1].lstrip())
    s.insert(1, delta*' '+'x=23\n')
    d = {}
    exec ''.join(s) in d
    return d[f.func_name]

    class X(object):

    @manip
    def a(self):
    print x

    @manip
    def b(self):
    print x+2

    x = X()
    x.a()
    x.b()


    The 'manip' example decorator places many implicit constraints on the
    sourcecode of the method that it decorates (single-line def clause, NO
    docstring to follow, no use of tabs for indentation but rather only
    spaces, etc, etc), which is why it's WAY over-fragile -- but, it does
    insert an 'x=23' assignment at the very top, as requested. It can, of
    course, be reimplemented in a MUCH more solid way, too (e.g., by using
    the Python interface to the AST compiler which seems likely to get
    introduced in Python 2.5 -- apparently, a prototype of such a Python
    module HAS been already implemented during Pycon, i.e., today).

    Personally, I would keep pushing back against this approach even after
    I'd gone to the trouble of implementing it more solidly -- in no way is
    clarity served by having magic local variables appear out of the blue
    (or, rather, the black of black magic). However, whether something CAN
    be done, and whether it SHOULD be done, are separate issues.


    Alex
     
    Alex Martelli, Feb 27, 2006
    #2
    1. Advertising

  3. Alex Martelli wrote:
    > Michal Kwiatkowski <> wrote:
    > ...
    >> def method(self):
    >> var_one = self.attr_one
    >> var_two = self.attr_two.another_attr
    >> empty_list = []
    >> # significant code goes here

    > ...


    > Personally, I would keep pushing back against this approach even after
    > I'd gone to the trouble of implementing it more solidly -- in no way is
    > clarity served by having magic local variables appear out of the blue
    > (or, rather, the black of black magic). However, whether something CAN
    > be done, and whether it SHOULD be done, are separate issues.


    I agree with your approach to local variables appearing out of nowhere,
    but still, it's a real pain to copy-and-paste few lines of this standard
    initialization to each new method I add. And then imagine another local
    variable will be needed and I have to change every single method...
    That's even more bug prone than magic-variables approach, if you ask me.
    The problem wouldn't be such a problem if Python had implicit self...
    but on the other side, it's another ambiguity.

    Well, maybe you can help me in refactoring this code, so that it won't
    be such a pain to easily modify groups of methods? I'm thinking about this:

    def init_arguments(fun):
    def new_f(self):
    var_one = self.attr_one
    var_two = self.attr_two.another_attr
    empty_list = []

    fun(self, var_one, var_two, empty_list)

    return new_f

    @init_arguments
    def method(self, var_one, var_two, empty_list):
    # significant code goes here
    # ...

    But the bad thing about this approach is that actual method doesn't
    really look like its definition, because of different number of arguments.

    mk
    --
    . o . >> http://joker.linuxstuff.pl <<
    . . o It's easier to get forgiveness for being wrong
    o o o than forgiveness for being right.
     
    Michal Kwiatkowski, Feb 27, 2006
    #3
  4. Michal Kwiatkowski <> wrote:
    ...
    > The problem wouldn't be such a problem if Python had implicit self...
    > but on the other side, it's another ambiguity.


    In your example, you could avoid assigning var_one, but the purpose of
    assigning var_two and empty_list obviously would not go away with
    implicit self (and attendant ambiguities).


    > Well, maybe you can help me in refactoring this code, so that it won't
    > be such a pain to easily modify groups of methods? I'm thinking about this:
    >
    > def init_arguments(fun):
    > def new_f(self):
    > var_one = self.attr_one
    > var_two = self.attr_two.another_attr
    > empty_list = []
    >
    > fun(self, var_one, var_two, empty_list)


    You probably want a 'return fun(...)' here, for generality.

    > return new_f
    >
    > @init_arguments
    > def method(self, var_one, var_two, empty_list):
    > # significant code goes here
    > # ...
    >
    > But the bad thing about this approach is that actual method doesn't
    > really look like its definition, because of different number of arguments.


    Yep, but to avoid black magic those names DO have to be identified as
    locals to the compiler at the time of the 'def method(...', which
    happens before the decorator gets to play. If you're keen on using
    those barenames as locals in method, there's no clean way out.

    Personally, I would give up on the bareness of the names and bunch them
    up into a specially named bunch, say _ (single underscore) assuming you
    don't need that for other purposes such as i18n. The method's signature
    would be a formulaic 'def method(_, self [other args if any])' -- or you
    could swap _ and self, if you wish; and the decorator could supply as _
    a magic object (nothing black about it;-) with the suitable behavior,
    not just for getting attributes, but also for setting them. If you're
    sure you don't need to set them, then a simple Bunch instance with the
    usual 'class Bunch: pass', or any other such approach, will suffice.

    But of course, then the method's body would have to use _.one rather
    than var_one, _.two rather than var_two, and _.empty_list rather than
    empty_list (what a strange name -- does it STAY empty throughout the
    method's execution?!). To me this looks like a small price to pay, and
    the 'def method(_, self, ...)' signature a clear, strong indication that
    something weird, but not TOO weird, is going on. YMMV...


    Alex
     
    Alex Martelli, Feb 27, 2006
    #4
  5. Alex Martelli wrote:
    > But of course, then the method's body would have to use _.one rather
    > than var_one, _.two rather than var_two, and _.empty_list rather than
    > empty_list (what a strange name -- does it STAY empty throughout the
    > method's execution?!). To me this looks like a small price to pay, and


    empty_list name was chosen only for sake of an example.

    > the 'def method(_, self, ...)' signature a clear, strong indication that
    > something weird, but not TOO weird, is going on. YMMV...


    Thanks for your reply, I'll consider your suggestion.

    mk
    --
    . o . >> http://joker.linuxstuff.pl <<
    . . o It's easier to get forgiveness for being wrong
    o o o than forgiveness for being right.
     
    Michal Kwiatkowski, Feb 27, 2006
    #5
  6. Michal Kwiatkowski wrote:
    > def init_arguments(fun):
    > def new_f(self):
    > var_one = self.attr_one
    > var_two = self.attr_two.another_attr
    > empty_list = []
    >
    > fun(self, var_one, var_two, empty_list)
    >
    > return new_f
    >
    > @init_arguments
    > def method(self, var_one, var_two, empty_list):
    > # significant code goes here
    > # ...
    >
    > But the bad thing about this approach is that actual method doesn't
    > really look like its definition, because of different number of arguments.


    I've come up with a solution that don't have this disadvantage. Here we go:

    def wrap_method(self, do):
    var_one = self.attr_one
    var_two = self.attr_two.another_attr
    empty_list = []

    return do(self, var_one, var_two, empty_list)

    def method(self):
    def do(self, var_one, var_two, empty_list):
    # significant code goes here
    # ...
    return self.wrap_method(do)

    All is implicit and have an advantage of being clear in < 2.4, where
    there are no nice @ decorator syntax. Again, simple things proved to be
    better. :)

    mk
    --
    . o . >> http://joker.linuxstuff.pl <<
    . . o It's easier to get forgiveness for being wrong
    o o o than forgiveness for being right.
     
    Michal Kwiatkowski, Feb 27, 2006
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Sullivan WxPyQtKinter
    Replies:
    10
    Views:
    671
    Antoon Pardon
    Nov 8, 2007
  2. auspicious
    Replies:
    0
    Views:
    325
    auspicious
    Sep 25, 2008
  3. Severin Ecker
    Replies:
    9
    Views:
    736
    James Kanze
    Mar 11, 2010
  4. Tammo Tjarks
    Replies:
    2
    Views:
    289
    Tammo Tjarks
    Sep 13, 2007
  5. Jean-Christophe

    Q: Local variables initialization shortcut.

    Jean-Christophe, Jun 2, 2012, in forum: C Programming
    Replies:
    48
    Views:
    904
Loading...

Share This Page