Default method arguments

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Benji York

    #16
    Re: Default method arguments

    bruno at modulix wrote:[color=blue]
    > Another solution to this is the use of a 'marker' object and identity[/color]
    test:[color=blue]
    >
    > _marker = []
    > class A(object):
    > def __init__(self, n):
    > self.data =n
    > def f(self, x = _marker):
    > if x is _marker:
    > x = self.data
    > print x[/color]

    I'll add my 2 cents to the mix:

    default = object()

    class A(object):
    def __init__(self, n):
    self.data = n

    def f(self, x=default):
    if x is default:
    x = self.data
    print x
    --
    Benji York

    Comment

    • Mike Meyer

      #17
      Re: Default method arguments

      gregory.petrosy an@gmail.com writes:
      [color=blue]
      > Hello everybody!
      > I have little problem:
      >
      > class A:
      > def __init__(self, n):
      > self.data = n
      > def f(self, x = ????)
      > print x
      >
      > All I want is to make self.data the default argument for self.f(). (I
      > want to use 'A' class as following :[/color]

      Store your default value in a container, and test for it:

      class A:
      _data = [None]
      def __init__(self, n):
      self._data = [n]
      def f(self, x = _data):
      if x is self._data:
      x = x[0]
      print x

      There are lots of variations on this theme.

      <mike
      --
      Mike Meyer <mwm@mired.or g> http://www.mired.org/home/mwm/
      Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.

      Comment

      • Mike Meyer

        #18
        Re: Default method arguments

        Benji York <benji@benjiyor k.com> writes:[color=blue]
        > I'll add my 2 cents to the mix:
        >
        > default = object()
        >
        > class A(object):
        > def __init__(self, n):
        > self.data = n
        >
        > def f(self, x=default):
        > if x is default:
        > x = self.data
        > print x[/color]

        There were a lot of solutions like this. I'd like to point out that
        you can put the "marker" in the class:

        class A(object):
        default = object()
        def __init__(self, n):
        self.data = n

        def f(self, x = default):
        if x is self.default:
        x = self.data
        print x

        This way you don't pollute the module namespace with class-specific
        names. You pollute the class namespace instead - which seems like an
        improvement.

        <mike
        --
        Mike Meyer <mwm@mired.or g> http://www.mired.org/home/mwm/
        Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.

        Comment

        • Gregory Petrosyan

          #19
          Re: Default method arguments

          I'm not very familiar with Python, so please explain me why should
          containers be used?
          For example in one of Paul Graham's essays there's an example of
          'generator of accumulators' in Python:

          def foo(n):
          s = [n]
          def bar(i):
          s[0] += i
          return s[0]
          return bar

          1) So, why just using 's = n' is not suitable? (It doesn't work, Python
          'doesn't see' s, but why?)
          2) Is 'foo.s = n' a correct solution? It seems to be a little more
          elegant. (I tested it, and it worked well)

          Sorry for possibly stupid questions.

          Comment

          • Mike Meyer

            #20
            Re: Default method arguments

            "Gregory Petrosyan" <gregory.petros yan@gmail.com> writes:
            [color=blue]
            > I'm not very familiar with Python, so please explain me why should
            > containers be used?
            > For example in one of Paul Graham's essays there's an example of
            > 'generator of accumulators' in Python:
            >
            > def foo(n):
            > s = [n]
            > def bar(i):
            > s[0] += i
            > return s[0]
            > return bar
            >
            > 1) So, why just using 's = n' is not suitable? (It doesn't work, Python
            > 'doesn't see' s, but why?)[/color]

            The Python assignment statements bind a name to a value. By default,
            they bind it in the current namespace. Doing "s = n" (or s <op>= n) in
            the function bar binds the name s in the function bar, and leaves the
            value in foo as it was. "s[0] = i" (or s[0 += i) binds the name s[0],
            not the name s, and hence mutates the object bound to s instead of
            binding s in the function bar's namespace. In reality, this is
            implemented by a mutator method of s, but it *looks* like you're
            binding s[0].
            [color=blue]
            > 2) Is 'foo.s = n' a correct solution? It seems to be a little more
            > elegant. (I tested it, and it worked well)[/color]

            It's basically the same solution. You're replacing binding a variable
            with mutating an object bound to a name in an outer scope. In one case
            the container is named s and is a list that you're setting an element
            of. In the other case, the container is named foo and is an object
            that you're setting an attribute on.

            <miker

            --
            Mike Meyer <mwm@mired.or g> http://www.mired.org/home/mwm/
            Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.

            Comment

            • Gregory Petrosyan

              #21
              Re: Default method arguments

              Thanks a lot, I understood the rule. Let's don't discuss this
              (containers etc.) anymore, or it'll be offtopic.

              Comment

              • Steven D'Aprano

                #22
                Re: Default method arguments

                On Tue, 15 Nov 2005 18:44:23 +0100, bruno at modulix wrote:
                [color=blue]
                > Another solution to this is the use of a 'marker' object and identity test:
                >
                > _marker = []
                > class A(object):
                > def __init__(self, n):
                > self.data =n
                > def f(self, x = _marker):
                > if x is _marker:
                > x = self.data
                > print x[/color]

                I would like to see _marker put inside the class' scope. That prevents
                somebody from the outside scope easily passing _marker as an argument to
                instance.f. It also neatly encapsulates everything A needs within A.

                class A(object):
                _marker = []
                def __init__(self, n):
                self.data =n
                def f(self, x = _marker):
                if x is self.__class__. _marker:
                # must use "is" and not "=="
                x = self.data
                print x

                Note the gotcha though: in the method definition, you refer to a plain
                _marker, but in the method code block, you need to qualify it.


                --
                Steven.

                Comment

                • Fredrik Lundh

                  #23
                  Re: Default method arguments

                  Steven D'Aprano wrote:
                  [color=blue][color=green]
                  >> Another solution to this is the use of a 'marker' object and identity test:
                  >>
                  >> _marker = []
                  >> class A(object):
                  >> def __init__(self, n):
                  >> self.data =n
                  >> def f(self, x = _marker):
                  >> if x is _marker:
                  >> x = self.data
                  >> print x[/color]
                  >
                  > I would like to see _marker put inside the class' scope. That prevents
                  > somebody from the outside scope easily passing _marker as an argument to
                  > instance.f.[/color]

                  if you don't want people to be able to easily pass _marker as an argument
                  to the f method, you probably shouldn't use it as the default value.

                  </F>



                  Comment

                  • Bengt Richter

                    #24
                    Re: Default method arguments

                    On Tue, 15 Nov 2005 23:51:18 +0100, "Fredrik Lundh" <fredrik@python ware.com> wrote:
                    [color=blue]
                    >Steven D'Aprano wrote:
                    >[color=green][color=darkred]
                    >>> Another solution to this is the use of a 'marker' object and identity test:
                    >>>
                    >>> _marker = []
                    >>> class A(object):
                    >>> def __init__(self, n):
                    >>> self.data =n
                    >>> def f(self, x = _marker):
                    >>> if x is _marker:
                    >>> x = self.data
                    >>> print x[/color]
                    >>
                    >> I would like to see _marker put inside the class' scope. That prevents
                    >> somebody from the outside scope easily passing _marker as an argument to
                    >> instance.f.[/color]
                    >
                    >if you don't want people to be able to easily pass _marker as an argument
                    >to the f method, you probably shouldn't use it as the default value.
                    >[/color]
                    LOL ;-)

                    Regards,
                    Bengt Richter

                    Comment

                    • Bengt Richter

                      #25
                      Re: Default method arguments

                      On Tue, 15 Nov 2005 23:51:18 +0100, "Fredrik Lundh" <fredrik@python ware.com> wrote:
                      [color=blue]
                      >Steven D'Aprano wrote:
                      >[color=green][color=darkred]
                      >>> Another solution to this is the use of a 'marker' object and identity test:
                      >>>
                      >>> _marker = []
                      >>> class A(object):
                      >>> def __init__(self, n):
                      >>> self.data =n
                      >>> def f(self, x = _marker):
                      >>> if x is _marker:
                      >>> x = self.data
                      >>> print x[/color]
                      >>
                      >> I would like to see _marker put inside the class' scope. That prevents
                      >> somebody from the outside scope easily passing _marker as an argument to
                      >> instance.f.[/color]
                      >
                      >if you don't want people to be able to easily pass _marker as an argument
                      >to the f method, you probably shouldn't use it as the default value.
                      >[/color]
                      LOL ;-)

                      Regards,
                      Bengt Richter

                      Comment

                      • Bengt Richter

                        #26
                        Re: Default method arguments

                        On 15 Nov 2005 11:02:38 -0800, "Martin Miller" <ggrp1.20.marti neau@dfgh.net> wrote:
                        [color=blue]
                        >Alex Martelli wrote, in part:[color=green]
                        >> If it's crucial to you to have some default argument value evaluated at
                        >> time X, then, by Python's simple rules, you know that you must arrange
                        >> for the 'def' statement itself to execute at time X. In this case, for
                        >> example, if being able to have self.data as the default argument value
                        >> is the crucial aspect of the program, you must ensure that the 'def'
                        >> runs AFTER self.data has the value you desire.
                        >>
                        >> For example:
                        >>
                        >> class A(object):
                        >> def __init__(self, n):
                        >> self.data = n
                        >> def f(self, x = self.data)
                        >> print x
                        >> self.f = f
                        >>
                        >> This way, of course, each instance a of class A will have a SEPARATE
                        >> callable attribute a.f which is the function you desire; this is
                        >> inevitable, since functions store their default argument values as part
                        >> of their per-function data. Since you want a.f and b.f to have
                        >> different default values for the argument (respectively a.data and
                        >> b.data), therefore a.f and b.f just cannot be the SAME function object
                        >> -- this is another way to look at your issue, in terms of what's stored
                        >> where rather than of what evaluates when, but of course it leads to
                        >> exactly the same conclusion.[/color]
                        >
                        >FWIT and ignoring the small typo on the inner def statement (the
                        >missing ':'), the example didn't work as I (and possibily others) might
                        >expect. Namely it doesn't make function f() a bound method of
                        >instances of class A, so calls to it don't receive an automatic 'self''
                        >argument when called on instances of class A.
                        >
                        >This is fairly easy to remedy use the standard new module thusly:
                        >
                        >import new
                        >class A(object):
                        > def __init__(self, n):
                        > self.data = n
                        > def f(self, x = self.data):
                        > print x
                        > self.f = new.instancemet hod(f, self, A)
                        >
                        >This change underscores the fact that each instance of class A gets a
                        >different independent f() method. Despite this nit, I believe I
                        >understand the points Alex makes about the subject (and would agree).
                        >[/color]
                        Or as Alex mentioned, a custom descriptor etc is possible, and can also
                        protect against replacing f by simple instance attribute assignment
                        like inst.f = something, or do some filtering to exclude non-function
                        assignments etc., e.g., (not sure what self.data is really
                        needed for, but we'll keep it):

                        BTW, note that self.data initially duplicates the default value,
                        but self.data per se is not used by the function (until the instance
                        method is replace by one that does, see further on)
                        [color=blue][color=green][color=darkred]
                        >>> class BindInstMethod( object):[/color][/color][/color]
                        ... def __init__(self, inst_fname):
                        ... self.inst_fname = inst_fname
                        ... def __get__(self, inst, cls=None):
                        ... if inst is None: return self
                        ... return inst.__dict__[self.inst_fname].__get__(inst, cls) # return bound instance method
                        ... def __set__(self, inst, val):
                        ... if not callable(val) or not hasattr(val, '__get__'): # screen out some impossible methods
                        ... raise AttributeError, '%s may not be replaced by %r' % (self.inst_fnam e, val)
                        ... inst.__dict__[self.inst_fname] = val
                        ...

                        The above class defines a custom descriptor that can be instatiated as a class
                        variable of a given name. When that name is thereafter accessed as an attribute
                        of an instance of the latter class (e.g. A below), the decriptor __get__ or __set__
                        methods will be called (the __set__ makes it a "data" descriptor, which intercepts
                        instance attribute assignment.
                        [color=blue][color=green][color=darkred]
                        >>> class A(object):[/color][/color][/color]
                        ... def __init__(self, n):
                        ... self.data = n
                        ... def f(self, x = self.data):
                        ... print x
                        ... self.__dict__['f'] = f # set instance attr w/o triggering descriptor
                        ... f = BindInstMethod( 'f')
                        ...
                        [color=blue][color=green][color=darkred]
                        >>> a = A(5)
                        >>> a.f[/color][/color][/color]
                        <bound method A.f of <__main__.A object at 0x02EF3B0C>>

                        Note that a.f is dynamically bound at the time of a.f access, not
                        retrieved as a prebound instance method.
                        [color=blue][color=green][color=darkred]
                        >>> a.f()[/color][/color][/color]
                        5[color=blue][color=green][color=darkred]
                        >>> a.f('not default 5')[/color][/color][/color]
                        not default 5[color=blue][color=green][color=darkred]
                        >>> a.data[/color][/color][/color]
                        5[color=blue][color=green][color=darkred]
                        >>> a.data = 'not original data 5'[/color][/color][/color]

                        Since the default is an independent duplicate of a.data
                        a call with no arg produces the original default:[color=blue][color=green][color=darkred]
                        >>> a.f()[/color][/color][/color]
                        5[color=blue][color=green][color=darkred]
                        >>> a.data[/color][/color][/color]
                        'not original data 5'[color=blue][color=green][color=darkred]
                        >>> a.f('this arg overrides the default')[/color][/color][/color]
                        this arg overrides the default

                        Try to change a.f[color=blue][color=green][color=darkred]
                        >>> a.f = 'sabotage f'[/color][/color][/color]
                        Traceback (most recent call last):
                        File "<stdin>", line 1, in ?
                        File "<stdin>", line 10, in __set__
                        AttributeError: f may not be replaced by 'sabotage f'

                        Now use a function, which should be accepted (note: a function, not an instance method)[color=blue][color=green][color=darkred]
                        >>> a.f = lambda self: self.data*2
                        >>> a.f[/color][/color][/color]
                        <bound method A.<lambda> of <__main__.A object at 0x02EF3B0C>>
                        Plainly the method was dynamically bound
                        [color=blue][color=green][color=darkred]
                        >>> a.f()[/color][/color][/color]
                        'not original data 5not original data 5'
                        That was self.data*2 per the lambda we just assigned to a.f
                        BTW, the assignment is not directly to the instance attribute.
                        It goes via the descriptor __set__ method.
                        [color=blue][color=green][color=darkred]
                        >>> a.data = 12
                        >>> a.f()[/color][/color][/color]
                        24[color=blue][color=green][color=darkred]
                        >>> b = A('bee')
                        >>> b.f[/color][/color][/color]
                        <bound method A.f of <__main__.A object at 0x02EF3BAC>>[color=blue][color=green][color=darkred]
                        >>> b.f()[/color][/color][/color]
                        bee[color=blue][color=green][color=darkred]
                        >>> b.f('not bee')[/color][/color][/color]
                        not bee[color=blue][color=green][color=darkred]
                        >>> b.data[/color][/color][/color]
                        'bee'[color=blue][color=green][color=darkred]
                        >>> b.data = 'no longer bee'
                        >>> b.f()[/color][/color][/color]
                        bee[color=blue][color=green][color=darkred]
                        >>> b.data[/color][/color][/color]
                        'no longer bee'[color=blue][color=green][color=darkred]
                        >>> b.f = lambda self: ' -- '.join([self.data]*3)
                        >>> b.data[/color][/color][/color]
                        'no longer bee'[color=blue][color=green][color=darkred]
                        >>> b.data = 'ha'
                        >>> b.f()[/color][/color][/color]
                        'ha -- ha -- ha'[color=blue][color=green][color=darkred]
                        >>> b.f = lambda self, n='default of n':n
                        >>> b.data[/color][/color][/color]
                        'ha'[color=blue][color=green][color=darkred]
                        >>> b.f(123)[/color][/color][/color]
                        123[color=blue][color=green][color=darkred]
                        >>> b.f()[/color][/color][/color]
                        'default of n'[color=blue][color=green][color=darkred]
                        >>> a.f()[/color][/color][/color]
                        24

                        Now let's add another name that can be used on instances like f[color=blue][color=green][color=darkred]
                        >>> A.g = BindInstMethod( 'g')
                        >>> a.g = lambda self:'a.g'
                        >>> a.f()[/color][/color][/color]
                        24[color=blue][color=green][color=darkred]
                        >>> a.g()[/color][/color][/color]
                        'a.g'[color=blue][color=green][color=darkred]
                        >>> a.g(123)[/color][/color][/color]
                        Traceback (most recent call last):
                        File "<stdin>", line 1, in ?
                        TypeError: <lambda>() takes exactly 1 argument (2 given)
                        Aha, the bound method got self as a first arg, but we defined g without any args.
                        [color=blue][color=green][color=darkred]
                        >>> b.g[/color][/color][/color]
                        Traceback (most recent call last):
                        File "<stdin>", line 1, in ?
                        File "<stdin>", line 7, in __get__
                        KeyError: 'g'
                        No instance method b.g defined yet (A.__init__ only defines f)

                        Make one with a default[color=blue][color=green][color=darkred]
                        >>> b.g = lambda self, x='xdefault': x
                        >>> b.g()[/color][/color][/color]
                        'xdefault'[color=blue][color=green][color=darkred]
                        >>> b.g('and arg')[/color][/color][/color]
                        'and arg'[color=blue][color=green][color=darkred]
                        >>> a.g()[/color][/color][/color]
                        'a.g'[color=blue][color=green][color=darkred]
                        >>>[/color][/color][/color]

                        If we bypass method assignment via the descriptor, we can sapotage it:
                        [color=blue][color=green][color=darkred]
                        >>> a.g = lambda self: 'this works'
                        >>> a.g()[/color][/color][/color]
                        'this works'[color=blue][color=green][color=darkred]
                        >>> a.g = 'sabotage'[/color][/color][/color]
                        Traceback (most recent call last):
                        File "<stdin>", line 1, in ?
                        File "<stdin>", line 10, in __set__
                        AttributeError: g may not be replaced by 'sabotage'
                        That was rejected

                        but,[color=blue][color=green][color=darkred]
                        >>> a.__dict__['g'] = 'sabotage' # this will bypass the descriptor
                        >>> a.g[/color][/color][/color]
                        Traceback (most recent call last):
                        File "<stdin>", line 1, in ?
                        File "<stdin>", line 7, in __get__
                        AttributeError: 'str' object has no attribute '__get__'
                        The descriptor couldn't form a bound method since 'sabotage' was not
                        a function or otherwise suitable.

                        But we can look at the instance attribute directly:[color=blue][color=green][color=darkred]
                        >>> a.__dict__['g'][/color][/color][/color]
                        'sabotage'

                        We could define __delete__ in the descriptor too, but didn't so
                        [color=blue][color=green][color=darkred]
                        >>> del a.g[/color][/color][/color]
                        Traceback (most recent call last):
                        File "<stdin>", line 1, in ?
                        AttributeError: __delete__

                        We could have made the descriptor return a.g if unable to form a bound method,
                        but then you'd probably want to permit arbitrary assignment to the descriptor-controlled
                        attributes too ;-)

                        Regards,
                        Bengt Richter

                        Comment

                        • Bengt Richter

                          #27
                          Re: Default method arguments

                          On 15 Nov 2005 11:02:38 -0800, "Martin Miller" <ggrp1.20.marti neau@dfgh.net> wrote:
                          [color=blue]
                          >Alex Martelli wrote, in part:[color=green]
                          >> If it's crucial to you to have some default argument value evaluated at
                          >> time X, then, by Python's simple rules, you know that you must arrange
                          >> for the 'def' statement itself to execute at time X. In this case, for
                          >> example, if being able to have self.data as the default argument value
                          >> is the crucial aspect of the program, you must ensure that the 'def'
                          >> runs AFTER self.data has the value you desire.
                          >>
                          >> For example:
                          >>
                          >> class A(object):
                          >> def __init__(self, n):
                          >> self.data = n
                          >> def f(self, x = self.data)
                          >> print x
                          >> self.f = f
                          >>
                          >> This way, of course, each instance a of class A will have a SEPARATE
                          >> callable attribute a.f which is the function you desire; this is
                          >> inevitable, since functions store their default argument values as part
                          >> of their per-function data. Since you want a.f and b.f to have
                          >> different default values for the argument (respectively a.data and
                          >> b.data), therefore a.f and b.f just cannot be the SAME function object
                          >> -- this is another way to look at your issue, in terms of what's stored
                          >> where rather than of what evaluates when, but of course it leads to
                          >> exactly the same conclusion.[/color]
                          >
                          >FWIT and ignoring the small typo on the inner def statement (the
                          >missing ':'), the example didn't work as I (and possibily others) might
                          >expect. Namely it doesn't make function f() a bound method of
                          >instances of class A, so calls to it don't receive an automatic 'self''
                          >argument when called on instances of class A.
                          >
                          >This is fairly easy to remedy use the standard new module thusly:
                          >
                          >import new
                          >class A(object):
                          > def __init__(self, n):
                          > self.data = n
                          > def f(self, x = self.data):
                          > print x
                          > self.f = new.instancemet hod(f, self, A)
                          >
                          >This change underscores the fact that each instance of class A gets a
                          >different independent f() method. Despite this nit, I believe I
                          >understand the points Alex makes about the subject (and would agree).
                          >[/color]
                          Or as Alex mentioned, a custom descriptor etc is possible, and can also
                          protect against replacing f by simple instance attribute assignment
                          like inst.f = something, or do some filtering to exclude non-function
                          assignments etc., e.g., (not sure what self.data is really
                          needed for, but we'll keep it):

                          BTW, note that self.data initially duplicates the default value,
                          but self.data per se is not used by the function (until the instance
                          method is replace by one that does, see further on)
                          [color=blue][color=green][color=darkred]
                          >>> class BindInstMethod( object):[/color][/color][/color]
                          ... def __init__(self, inst_fname):
                          ... self.inst_fname = inst_fname
                          ... def __get__(self, inst, cls=None):
                          ... if inst is None: return self
                          ... return inst.__dict__[self.inst_fname].__get__(inst, cls) # return bound instance method
                          ... def __set__(self, inst, val):
                          ... if not callable(val) or not hasattr(val, '__get__'): # screen out some impossible methods
                          ... raise AttributeError, '%s may not be replaced by %r' % (self.inst_fnam e, val)
                          ... inst.__dict__[self.inst_fname] = val
                          ...

                          The above class defines a custom descriptor that can be instatiated as a class
                          variable of a given name. When that name is thereafter accessed as an attribute
                          of an instance of the latter class (e.g. A below), the decriptor __get__ or __set__
                          methods will be called (the __set__ makes it a "data" descriptor, which intercepts
                          instance attribute assignment.
                          [color=blue][color=green][color=darkred]
                          >>> class A(object):[/color][/color][/color]
                          ... def __init__(self, n):
                          ... self.data = n
                          ... def f(self, x = self.data):
                          ... print x
                          ... self.__dict__['f'] = f # set instance attr w/o triggering descriptor
                          ... f = BindInstMethod( 'f')
                          ...
                          [color=blue][color=green][color=darkred]
                          >>> a = A(5)
                          >>> a.f[/color][/color][/color]
                          <bound method A.f of <__main__.A object at 0x02EF3B0C>>

                          Note that a.f is dynamically bound at the time of a.f access, not
                          retrieved as a prebound instance method.
                          [color=blue][color=green][color=darkred]
                          >>> a.f()[/color][/color][/color]
                          5[color=blue][color=green][color=darkred]
                          >>> a.f('not default 5')[/color][/color][/color]
                          not default 5[color=blue][color=green][color=darkred]
                          >>> a.data[/color][/color][/color]
                          5[color=blue][color=green][color=darkred]
                          >>> a.data = 'not original data 5'[/color][/color][/color]

                          Since the default is an independent duplicate of a.data
                          a call with no arg produces the original default:[color=blue][color=green][color=darkred]
                          >>> a.f()[/color][/color][/color]
                          5[color=blue][color=green][color=darkred]
                          >>> a.data[/color][/color][/color]
                          'not original data 5'[color=blue][color=green][color=darkred]
                          >>> a.f('this arg overrides the default')[/color][/color][/color]
                          this arg overrides the default

                          Try to change a.f[color=blue][color=green][color=darkred]
                          >>> a.f = 'sabotage f'[/color][/color][/color]
                          Traceback (most recent call last):
                          File "<stdin>", line 1, in ?
                          File "<stdin>", line 10, in __set__
                          AttributeError: f may not be replaced by 'sabotage f'

                          Now use a function, which should be accepted (note: a function, not an instance method)[color=blue][color=green][color=darkred]
                          >>> a.f = lambda self: self.data*2
                          >>> a.f[/color][/color][/color]
                          <bound method A.<lambda> of <__main__.A object at 0x02EF3B0C>>
                          Plainly the method was dynamically bound
                          [color=blue][color=green][color=darkred]
                          >>> a.f()[/color][/color][/color]
                          'not original data 5not original data 5'
                          That was self.data*2 per the lambda we just assigned to a.f
                          BTW, the assignment is not directly to the instance attribute.
                          It goes via the descriptor __set__ method.
                          [color=blue][color=green][color=darkred]
                          >>> a.data = 12
                          >>> a.f()[/color][/color][/color]
                          24[color=blue][color=green][color=darkred]
                          >>> b = A('bee')
                          >>> b.f[/color][/color][/color]
                          <bound method A.f of <__main__.A object at 0x02EF3BAC>>[color=blue][color=green][color=darkred]
                          >>> b.f()[/color][/color][/color]
                          bee[color=blue][color=green][color=darkred]
                          >>> b.f('not bee')[/color][/color][/color]
                          not bee[color=blue][color=green][color=darkred]
                          >>> b.data[/color][/color][/color]
                          'bee'[color=blue][color=green][color=darkred]
                          >>> b.data = 'no longer bee'
                          >>> b.f()[/color][/color][/color]
                          bee[color=blue][color=green][color=darkred]
                          >>> b.data[/color][/color][/color]
                          'no longer bee'[color=blue][color=green][color=darkred]
                          >>> b.f = lambda self: ' -- '.join([self.data]*3)
                          >>> b.data[/color][/color][/color]
                          'no longer bee'[color=blue][color=green][color=darkred]
                          >>> b.data = 'ha'
                          >>> b.f()[/color][/color][/color]
                          'ha -- ha -- ha'[color=blue][color=green][color=darkred]
                          >>> b.f = lambda self, n='default of n':n
                          >>> b.data[/color][/color][/color]
                          'ha'[color=blue][color=green][color=darkred]
                          >>> b.f(123)[/color][/color][/color]
                          123[color=blue][color=green][color=darkred]
                          >>> b.f()[/color][/color][/color]
                          'default of n'[color=blue][color=green][color=darkred]
                          >>> a.f()[/color][/color][/color]
                          24

                          Now let's add another name that can be used on instances like f[color=blue][color=green][color=darkred]
                          >>> A.g = BindInstMethod( 'g')
                          >>> a.g = lambda self:'a.g'
                          >>> a.f()[/color][/color][/color]
                          24[color=blue][color=green][color=darkred]
                          >>> a.g()[/color][/color][/color]
                          'a.g'[color=blue][color=green][color=darkred]
                          >>> a.g(123)[/color][/color][/color]
                          Traceback (most recent call last):
                          File "<stdin>", line 1, in ?
                          TypeError: <lambda>() takes exactly 1 argument (2 given)
                          Aha, the bound method got self as a first arg, but we defined g without any args.
                          [color=blue][color=green][color=darkred]
                          >>> b.g[/color][/color][/color]
                          Traceback (most recent call last):
                          File "<stdin>", line 1, in ?
                          File "<stdin>", line 7, in __get__
                          KeyError: 'g'
                          No instance method b.g defined yet (A.__init__ only defines f)

                          Make one with a default[color=blue][color=green][color=darkred]
                          >>> b.g = lambda self, x='xdefault': x
                          >>> b.g()[/color][/color][/color]
                          'xdefault'[color=blue][color=green][color=darkred]
                          >>> b.g('and arg')[/color][/color][/color]
                          'and arg'[color=blue][color=green][color=darkred]
                          >>> a.g()[/color][/color][/color]
                          'a.g'[color=blue][color=green][color=darkred]
                          >>>[/color][/color][/color]

                          If we bypass method assignment via the descriptor, we can sapotage it:
                          [color=blue][color=green][color=darkred]
                          >>> a.g = lambda self: 'this works'
                          >>> a.g()[/color][/color][/color]
                          'this works'[color=blue][color=green][color=darkred]
                          >>> a.g = 'sabotage'[/color][/color][/color]
                          Traceback (most recent call last):
                          File "<stdin>", line 1, in ?
                          File "<stdin>", line 10, in __set__
                          AttributeError: g may not be replaced by 'sabotage'
                          That was rejected

                          but,[color=blue][color=green][color=darkred]
                          >>> a.__dict__['g'] = 'sabotage' # this will bypass the descriptor
                          >>> a.g[/color][/color][/color]
                          Traceback (most recent call last):
                          File "<stdin>", line 1, in ?
                          File "<stdin>", line 7, in __get__
                          AttributeError: 'str' object has no attribute '__get__'
                          The descriptor couldn't form a bound method since 'sabotage' was not
                          a function or otherwise suitable.

                          But we can look at the instance attribute directly:[color=blue][color=green][color=darkred]
                          >>> a.__dict__['g'][/color][/color][/color]
                          'sabotage'

                          We could define __delete__ in the descriptor too, but didn't so
                          [color=blue][color=green][color=darkred]
                          >>> del a.g[/color][/color][/color]
                          Traceback (most recent call last):
                          File "<stdin>", line 1, in ?
                          AttributeError: __delete__

                          We could have made the descriptor return a.g if unable to form a bound method,
                          but then you'd probably want to permit arbitrary assignment to the descriptor-controlled
                          attributes too ;-)

                          Regards,
                          Bengt Richter

                          Comment

                          • bonono@gmail.com

                            #28
                            Re: Default method arguments

                            What you want is essentially :

                            if parm_x is not supplied, use self.val_x

                            So why not just express it clearly at the very beginning of the
                            function :

                            def f(self, parm_x=NotSuppl ied, parm_y=NotSuppl ied ,,,)
                            if parm_x is NotSupplied: parm_x = self.val_x
                            if parm_y is NotSupplied: parm_y = self.val_y

                            Much easier to understand than the "twisting your arm 720 degree in the
                            back" factory method, IMO.

                            Gregory Petrosyan wrote:[color=blue]
                            > Thanks a lot, but that's not what I do really want.
                            > 1) f() may have many arguments, not one
                            > 2) I don't whant only to _print_ x. I want to do many work with it, so
                            > if I could simply write
                            >
                            > def f(self, x = self.data) (*)
                            >
                            > it would be much better.
                            >
                            > By the way, using
                            >
                            > class A(object):
                            > data = 0
                            > ....
                            > def f(self, x = data)
                            >
                            > solves this problem, but not nice at all
                            >
                            > So I think (*) is the best variant, but it doesn't work :([/color]

                            Comment

                            • bonono@gmail.com

                              #29
                              Re: Default method arguments

                              What you want is essentially :

                              if parm_x is not supplied, use self.val_x

                              So why not just express it clearly at the very beginning of the
                              function :

                              def f(self, parm_x=NotSuppl ied, parm_y=NotSuppl ied ,,,)
                              if parm_x is NotSupplied: parm_x = self.val_x
                              if parm_y is NotSupplied: parm_y = self.val_y

                              Much easier to understand than the "twisting your arm 720 degree in the
                              back" factory method, IMO.

                              Gregory Petrosyan wrote:[color=blue]
                              > Thanks a lot, but that's not what I do really want.
                              > 1) f() may have many arguments, not one
                              > 2) I don't whant only to _print_ x. I want to do many work with it, so
                              > if I could simply write
                              >
                              > def f(self, x = self.data) (*)
                              >
                              > it would be much better.
                              >
                              > By the way, using
                              >
                              > class A(object):
                              > data = 0
                              > ....
                              > def f(self, x = data)
                              >
                              > solves this problem, but not nice at all
                              >
                              > So I think (*) is the best variant, but it doesn't work :([/color]

                              Comment

                              • Duncan Booth

                                #30
                                Re: Default method arguments

                                Steven D'Aprano wrote:[color=blue]
                                > I would like to see _marker put inside the class' scope. That prevents
                                > somebody from the outside scope easily passing _marker as an argument
                                > to instance.f. It also neatly encapsulates everything A needs within
                                > A.[/color]

                                Surely that makes it easier for someone outside the scope to pass in
                                marker:

                                class A(object):
                                _marker = []
                                def __init__(self, n):
                                self.data =n
                                def f(self, x = _marker):
                                if x is self.__class__. _marker:
                                # must use "is" and not "=="
                                x = self.data
                                print x
                                [color=blue][color=green][color=darkred]
                                >>> instance = A(5)
                                >>> instance.f(inst ance._marker)[/color][/color][/color]
                                5

                                What you really want is for the marker to exist only in its own little
                                universe, but the code for that is even messier:

                                class A(object):
                                def __init__(self, n):
                                self.data =n
                                def make_f():
                                marker = object()
                                def f(self, x = _marker):
                                if x is _marker:
                                x = self.data
                                print x
                                return f
                                f = make_f()

                                [color=blue][color=green][color=darkred]
                                >>> instance = A(6)
                                >>> instance.f()[/color][/color][/color]
                                6

                                Comment

                                Working...