__getitem__ method on (meta)classes

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • ron@flownet.com

    __getitem__ method on (meta)classes


    Why doesn't this work?
    [color=blue][color=green][color=darkred]
    >>> def foo(lst):[/color][/color][/color]
    .... class baz(object):
    .... def __getitem__(cls , idx): return cls.lst[idx]
    .... __getitem__=cla ssmethod(__geti tem__)
    .... baz.lst = lst
    .... return baz
    ....[color=blue][color=green][color=darkred]
    >>> f = foo([1,2,3])
    >>> f[0][/color][/color][/color]
    Traceback (most recent call last):
    File "<stdin>", line 1, in ?
    TypeError: unsubscriptable object[color=blue][color=green][color=darkred]
    >>> f.__getitem__(0 )[/color][/color][/color]
    1[color=blue][color=green][color=darkred]
    >>>[/color][/color][/color]


    I thought x[y] and x.__getitem__(y ) were supposed to always be
    synonymous.

    Thanks,
    rg

  • Simon Percivall

    #2
    Re: __getitem__ method on (meta)classes

    Well, they're not synonymous. At least not in that context. If you
    haven't already tried it, what you're doing will fail for instances as
    well. Look in typeobject.c to see why. The gist of it is that the
    special methods are looked up on the type rather than the instance (on
    the metaclass rather than on the class, in your case), so the internal
    lookup mechanism ignore instance attributes completely in this case.

    Comment

    • Bengt Richter

      #3
      Re: __getitem__ method on (meta)classes

      On 14 Mar 2005 17:43:53 -0800, ron@flownet.com wrote:
      [color=blue]
      >
      >Why doesn't this work?
      >[color=green][color=darkred]
      >>>> def foo(lst):[/color][/color]
      >... class baz(object):
      >... def __getitem__(cls , idx): return cls.lst[idx]
      >... __getitem__=cla ssmethod(__geti tem__)
      >... baz.lst = lst
      >... return baz
      >...[color=green][color=darkred]
      >>>> f = foo([1,2,3])
      >>>> f[0][/color][/color]
      >Traceback (most recent call last):
      > File "<stdin>", line 1, in ?
      >TypeError: unsubscriptable object[color=green][color=darkred]
      >>>> f.__getitem__(0 )[/color][/color]
      >1[color=green][color=darkred]
      >>>>[/color][/color]
      >
      >
      >I thought x[y] and x.__getitem__(y ) were supposed to always be
      >synonymous.[/color]
      Yes, but what was your "x"?
      Note:
      [color=blue][color=green][color=darkred]
      >>> def foo(lst):[/color][/color][/color]
      ... class baz(object):
      ... def __getitem__(cls , idx): return cls.lst[idx]
      ... __getitem__ = classmethod(__g etitem__)
      ... baz.lst = lst
      ... return baz
      ...[color=blue][color=green][color=darkred]
      >>> f = foo([1,2,3])
      >>> f[/color][/color][/color]
      <class '__main__.baz'>

      Your "x" was the baz *class*, and the baz *class* is not subscriptable *itself*,
      even though it defines subscripting for its instances.
      To be ordinarily subscriptable, type(f).__getit em__ would have to succeed, which it doesn't:
      [color=blue][color=green][color=darkred]
      >>> type(f)[/color][/color][/color]
      <type 'type'>[color=blue][color=green][color=darkred]
      >>> type(f).__getit em__[/color][/color][/color]
      Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      AttributeError: type object 'type' has no attribute '__getitem__'

      However, a baz instance is different:
      [color=blue][color=green][color=darkred]
      >>> finst = f()
      >>> finst[/color][/color][/color]
      <__main__.baz object at 0x02EF168C>

      Its type does have a __getitem__ attribute:
      [color=blue][color=green][color=darkred]
      >>> type(finst).__g etitem__[/color][/color][/color]
      <bound method type.__getitem_ _ of <class '__main__.baz'> >

      I think "bound method" is a bit of a misnomer for a classmethod, though
      it is a method, and it is bound, just to the class instead of the instance.
      (see below)
      [color=blue][color=green][color=darkred]
      >>> type(finst).__g etitem__(0)[/color][/color][/color]
      1

      Or straightforward indexing syntax:
      [color=blue][color=green][color=darkred]
      >>> finst[0], finst[1], finst[2][/color][/color][/color]
      (1, 2, 3)

      Contrast with the way an ordinary method repr's when you access it
      via class and instance:
      [color=blue][color=green][color=darkred]
      >>> class C(object):[/color][/color][/color]
      ... def ordinary_method (self): return 'Hi'
      ...[color=blue][color=green][color=darkred]
      >>> c=C()
      >>> C.ordinary_meth od[/color][/color][/color]
      <unbound method C.ordinary_meth od>[color=blue][color=green][color=darkred]
      >>> c.ordinary_meth od[/color][/color][/color]
      <bound method C.ordinary_meth od of <__main__.C object at 0x02EF164C>>[color=blue][color=green][color=darkred]
      >>> c.ordinary_meth od()[/color][/color][/color]
      'Hi'[color=blue][color=green][color=darkred]
      >>> C.ordinary_meth od[/color][/color][/color]
      <unbound method C.ordinary_meth od>[color=blue][color=green][color=darkred]
      >>> C.ordinary_meth od(c)[/color][/color][/color]
      'Hi'

      Note the difference between ordinary and classmethod:
      [color=blue][color=green][color=darkred]
      >>> type(c).ordinar y_method[/color][/color][/color]
      <unbound method C.ordinary_meth od>[color=blue][color=green][color=darkred]
      >>> type(finst).__g etitem__[/color][/color][/color]
      <bound method type.__getitem_ _ of <class '__main__.baz'> >

      BTW, I see a class factory, but no "(meta)"cla ss per se.

      Regards,
      Bengt Richter

      Comment

      • Leif K-Brooks

        #4
        Re: __getitem__ method on (meta)classes

        ron@flownet.com wrote:[color=blue]
        > Why doesn't this work?
        >
        >[color=green][color=darkred]
        >>>>def foo(lst):[/color][/color]
        >
        > ... class baz(object):
        > ... def __getitem__(cls , idx): return cls.lst[idx]
        > ... __getitem__=cla ssmethod(__geti tem__)
        > ... baz.lst = lst
        > ... return baz
        > ...
        >
        > I thought x[y] and x.__getitem__(y ) were supposed to always be
        > synonymous.[/color]

        No, with new-style classes, x[y] and type(x).__getit em__(y) are
        synonymous. This works:
        [color=blue][color=green][color=darkred]
        >>> def foo(lst):[/color][/color][/color]
        .... class bar(type):
        .... def __getitem__(sel f, index):
        .... return self.lst[index]
        .... class baz(object):
        .... __metaclass__ = bar
        .... baz.lst = lst
        .... return baz
        ....[color=blue][color=green][color=darkred]
        >>> foo([1,2,3])[0][/color][/color][/color]
        1

        Comment

        • Michele Simionato

          #5
          Re: __getitem__ method on (meta)classes

          Leif Brooks:[color=blue]
          >with new-style classes, x[y] and type(x).__getit em__(y) are
          >synonymous.[/color]

          Yes, but check the discussion around SF789262. The call ``x[y]`` is
          converted to ``type(x).__get item__(x,y)``
          *only if* ``__getitem__`` is explicitely defined in ``type(x)``. If
          ``type(x)`` does not define ``__getitem__`` directly, but only
          indirectly via delegation (i.e. overriding ``__getattribut e__``),
          then the second form (i.e. ``type(x).__get item__(x,y)``) works but
          not the first one (i.e. ``x[y]`` raises an error).

          Michele Simionato

          Comment

          • Ron Garret

            #6
            Re: __getitem__ method on (meta)classes

            In article <39n1jpF60pc7pU 1@individual.ne t>,
            Leif K-Brooks <eurleif@ecritt ers.biz> wrote:
            [color=blue]
            > ron@flownet.com wrote:[color=green]
            > > Why doesn't this work?
            > >
            > >[color=darkred]
            > >>>>def foo(lst):[/color]
            > >
            > > ... class baz(object):
            > > ... def __getitem__(cls , idx): return cls.lst[idx]
            > > ... __getitem__=cla ssmethod(__geti tem__)
            > > ... baz.lst = lst
            > > ... return baz
            > > ...
            > >
            > > I thought x[y] and x.__getitem__(y ) were supposed to always be
            > > synonymous.[/color]
            >
            > No, with new-style classes, x[y] and type(x).__getit em__(y) are
            > synonymous.[/color]

            Ah.

            Did you mean type(x).__getit em__(x,y)?

            And where is this documented?

            rg

            Comment

            • Ron Garret

              #7
              Re: __getitem__ method on (meta)classes

              In article <423643e7.80503 2315@news.oz.ne t>,
              bokr@oz.net (Bengt Richter) wrote:
              [color=blue]
              > On 14 Mar 2005 17:43:53 -0800, ron@flownet.com wrote:
              >[color=green]
              > >
              > >Why doesn't this work?
              > >[color=darkred]
              > >>>> def foo(lst):[/color]
              > >... class baz(object):
              > >... def __getitem__(cls , idx): return cls.lst[idx]
              > >... __getitem__=cla ssmethod(__geti tem__)
              > >... baz.lst = lst
              > >... return baz
              > >...[color=darkred]
              > >>>> f = foo([1,2,3])
              > >>>> f[0][/color]
              > >Traceback (most recent call last):
              > > File "<stdin>", line 1, in ?
              > >TypeError: unsubscriptable object[color=darkred]
              > >>>> f.__getitem__(0 )[/color]
              > >1[color=darkred]
              > >>>>[/color]
              > >
              > >
              > >I thought x[y] and x.__getitem__(y ) were supposed to always be
              > >synonymous.[/color]
              > Yes, but what was your "x"?
              > Note:
              >[color=green][color=darkred]
              > >>> def foo(lst):[/color][/color]
              > ... class baz(object):
              > ... def __getitem__(cls , idx): return cls.lst[idx]
              > ... __getitem__ = classmethod(__g etitem__)
              > ... baz.lst = lst
              > ... return baz
              > ...[color=green][color=darkred]
              > >>> f = foo([1,2,3])
              > >>> f[/color][/color]
              > <class '__main__.baz'>
              >
              > Your "x" was the baz *class*, and the baz *class* is not subscriptable
              > *itself*,[/color]

              But why not? __getitem__ is a class method, not an instance method.

              (You can treat that as a rhetorical question. I know the answer already
              from other postings in this thread.)
              [color=blue]
              > BTW, I see a class factory, but no "(meta)"cla ss per se.[/color]

              That's because I took out extraneous code to not distract from the
              problem I was having, and in the process took out all the useful stuff
              :-)

              What I'm really trying to do is to create enumerated types such that if:

              e1 = enum(lst) and v = e1(x)

              then

              (x in lst) and (e1[v] == x)

              In other words, I want to map values onto their representations using
              the () operator, and representations onto their values using the []
              operator. That requires me to define the [] operator on the enumerated
              classes themselves. So my actual code was:

              def enum(vals):
              class enum(object):
              def __init__(self, val):
              try:
              self.val = type(self).vals .index(val)
              except:
              raise TypeError, "%s is not a valid %s" % (val, type(self))
              def __getitem__(sel f, index): return self.vals.index (index)
              __getitem__=cla ssmethod(__geti tem__)
              enum.vals = vals
              return enum


              (Actually, what I'm really trying to do is create a whole hierarchy of
              static type descriptors and automatically generate database schema from
              those descriptors, but that's a story for another day.)

              Thanks for all the responses.

              rg

              Comment

              • Steven Bethard

                #8
                Re: __getitem__ method on (meta)classes

                Ron Garret wrote:[color=blue]
                > What I'm really trying to do is to create enumerated types such that if:
                >
                > e1 = enum(lst) and v = e1(x)
                >
                > then
                >
                > (x in lst) and (e1[v] == x)[/color]

                Use a class with __call__ and __getitem__:

                py> class enum(object):
                .... def __init__(self, vals):
                .... self.vals = vals
                .... def __call__(self, val):
                .... return self.vals.index (val)
                .... def __getitem__(sel f, index):
                .... return self.vals[index]
                ....
                py> lst = 'abcd'
                py> x = 'b'
                py> e1 = enum(lst)
                py> v = e1(x)
                py> (x in lst) and (e1[v] == x)
                True

                STeVe

                Comment

                • Bengt Richter

                  #9
                  Re: __getitem__ method on (meta)classes

                  On Mon, 14 Mar 2005 22:00:38 -0800, Ron Garret <rNOSPAMon@flow net.com> wrote:
                  [color=blue]
                  >In article <39n1jpF60pc7pU 1@individual.ne t>,
                  > Leif K-Brooks <eurleif@ecritt ers.biz> wrote:
                  >[color=green]
                  >> ron@flownet.com wrote:[color=darkred]
                  >> > Why doesn't this work?
                  >> >
                  >> >
                  >> >>>>def foo(lst):
                  >> >
                  >> > ... class baz(object):
                  >> > ... def __getitem__(cls , idx): return cls.lst[idx]
                  >> > ... __getitem__=cla ssmethod(__geti tem__)
                  >> > ... baz.lst = lst
                  >> > ... return baz
                  >> > ...
                  >> >
                  >> > I thought x[y] and x.__getitem__(y ) were supposed to always be
                  >> > synonymous.[/color]
                  >>
                  >> No, with new-style classes, x[y] and type(x).__getit em__(y) are
                  >> synonymous.[/color]
                  >
                  >Ah.
                  >
                  >Did you mean type(x).__getit em__(x,y)?
                  >[/color]
                  Not if x is a classmethod, since type(x).__getit em__ gets you a bound-to-the-class method
                  instead of the usual unbound method, which would want the x instance as the first argument.
                  [color=blue][color=green][color=darkred]
                  >>> def foo(lst):[/color][/color][/color]
                  ... class baz(object):
                  ... def __getitem__(cls , idx): return cls.lst[idx]
                  ... __getitem__=cla ssmethod(__geti tem__)
                  ... baz.lst = lst
                  ... return baz
                  ...[color=blue][color=green][color=darkred]
                  >>> f = foo([1,2,3])()
                  >>> type(f).__getit em__[/color][/color][/color]
                  <bound method type.__getitem_ _ of <class '__main__.baz'> >[color=blue][color=green][color=darkred]
                  >>> type(f).__getit em__(0)[/color][/color][/color]
                  1

                  Leaving out the classmethod:
                  [color=blue][color=green][color=darkred]
                  >>> def foo(lst):[/color][/color][/color]
                  ... class baz(object):
                  ... def __getitem__(cls , idx): return cls.lst[idx]
                  ... baz.lst = lst
                  ... return baz
                  ...[color=blue][color=green][color=darkred]
                  >>> f = foo([1,2,3])()
                  >>> type(f).__getit em__[/color][/color][/color]
                  <unbound method baz.__getitem__ >[color=blue][color=green][color=darkred]
                  >>> type(f).__getit em__(f, 0)[/color][/color][/color]
                  1
                  [color=blue]
                  >And where is this documented?[/color]
                  Between the lines in my previous post ;-)


                  Regards,
                  Bengt Richter

                  Comment

                  • Ron Garret

                    #10
                    Re: __getitem__ method on (meta)classes

                    In article <rNOSPAMon-E9EF21.22143514 032005@news.gha .chartermi.net> ,
                    Ron Garret <rNOSPAMon@flow net.com> wrote:

                    Wow, this is really cool:
                    [color=blue]
                    > What I'm really trying to do is to create enumerated types...[/color]

                    And the code I ended up with is:

                    # Inheriting from type, not object, is the key:
                    class enum_metaclass( type):
                    def __getitem__(sel f, index):
                    return self.vals[index]

                    def enum(vals):
                    class enum(object):
                    __metaclass__ = enum_metaclass
                    def __init__(self, val):
                    try:
                    self.val = type(self).vals .index(val)
                    except:
                    raise TypeError, "%s is not a valid %s" % (val, type(self))
                    enum.vals = vals
                    return enum


                    So now I can do, e.g.:
                    [color=blue][color=green][color=darkred]
                    >>> e1 = enum(['a','b','c'])
                    >>> e1('a')[/color][/color][/color]
                    <__main__.enu m object at 0x393c50>[color=blue][color=green][color=darkred]
                    >>> e1('x')[/color][/color][/color]
                    Traceback (most recent call last):
                    File "<stdin>", line 1, in ?
                    File "<stdin>", line 8, in __init__
                    TypeError: x is not a valid <class '__main__.enum' >[color=blue][color=green][color=darkred]
                    >>>[/color][/color][/color]

                    But I can also do this:
                    [color=blue][color=green][color=darkred]
                    >>> for x in e1: print x[/color][/color][/color]
                    ....
                    a
                    b
                    c[color=blue][color=green][color=darkred]
                    >>>[/color][/color][/color]

                    Note that there's no __iter__ method anywhere! It makes an interesting
                    little puzzle to figure out why this works. (It totally blew me away
                    when I first tried it. Took me about five minutes of head scratching to
                    figure it out.)

                    rg

                    Comment

                    • Steven Bethard

                      #11
                      Re: __getitem__ method on (meta)classes

                      Ron Garret wrote:[color=blue]
                      > And the code I ended up with is:
                      >
                      > # Inheriting from type, not object, is the key:
                      > class enum_metaclass( type):
                      > def __getitem__(sel f, index):
                      > return self.vals[index]
                      >
                      > def enum(vals):
                      > class enum(object):
                      > __metaclass__ = enum_metaclass
                      > def __init__(self, val):
                      > try:
                      > self.val = type(self).vals .index(val)
                      > except:
                      > raise TypeError, "%s is not a valid %s" % (val, type(self))
                      > enum.vals = vals
                      > return enum[/color]

                      A good example of why 99.9% of people don't need metaclasses. See my
                      solution using __call__ in the other post.
                      [color=blue]
                      > Note that there's no __iter__ method anywhere! It makes an interesting
                      > little puzzle to figure out why this works. (It totally blew me away
                      > when I first tried it. Took me about five minutes of head scratching to
                      > figure it out.)[/color]

                      Best to add an __iter__ if you want one. The __getitem__ protocol is
                      basically deprecated though it works for backwards compatibility.

                      STeVe

                      Comment

                      • Ron Garret

                        #12
                        Re: __getitem__ method on (meta)classes

                        In article <423684d8.82165 7701@news.oz.ne t>,
                        bokr@oz.net (Bengt Richter) wrote:
                        [color=blue][color=green]
                        > >Did you mean type(x).__getit em__(x,y)?
                        > >[/color]
                        > Not if x is a classmethod,[/color]

                        Oh yeah, right. Duh!
                        [color=blue][color=green]
                        > >And where is this documented?[/color]
                        > Between the lines in my previous post ;-)[/color]

                        I see. I guess I wasn't asking a stupid question then :-)

                        rg

                        Comment

                        • Bengt Richter

                          #13
                          Re: __getitem__ method on (meta)classes

                          On Mon, 14 Mar 2005 23:44:46 -0700, Steven Bethard <steven.bethard @gmail.com> wrote:
                          [color=blue]
                          >Ron Garret wrote:[color=green]
                          >> What I'm really trying to do is to create enumerated types such that if:
                          >>
                          >> e1 = enum(lst) and v = e1(x)
                          >>
                          >> then
                          >>
                          >> (x in lst) and (e1[v] == x)[/color]
                          >
                          >Use a class with __call__ and __getitem__:
                          >
                          >py> class enum(object):
                          >... def __init__(self, vals):
                          >... self.vals = vals
                          >... def __call__(self, val):
                          >... return self.vals.index (val)
                          >... def __getitem__(sel f, index):
                          >... return self.vals[index]
                          >...
                          >py> lst = 'abcd'
                          >py> x = 'b'
                          >py> e1 = enum(lst)
                          >py> v = e1(x)
                          >py> (x in lst) and (e1[v] == x)
                          >True[/color]

                          For that, why not just

                          class enum(list):
                          def __call__(self, val): return self.index(val)

                          Regards,
                          Bengt Richter

                          Comment

                          • Ron Garret

                            #14
                            Re: __getitem__ method on (meta)classes

                            In article <Sf6dneCcy-DEGavfRVn-iw@comcast.com> ,
                            Steven Bethard <steven.bethard @gmail.com> wrote:
                            [color=blue]
                            > Ron Garret wrote:[color=green]
                            > > What I'm really trying to do is to create enumerated types such that if:
                            > >
                            > > e1 = enum(lst) and v = e1(x)
                            > >
                            > > then
                            > >
                            > > (x in lst) and (e1[v] == x)[/color]
                            >
                            > Use a class with __call__ and __getitem__:
                            >
                            > py> class enum(object):
                            > ... def __init__(self, vals):
                            > ... self.vals = vals
                            > ... def __call__(self, val):
                            > ... return self.vals.index (val)
                            > ... def __getitem__(sel f, index):
                            > ... return self.vals[index]
                            > ...
                            > py> lst = 'abcd'
                            > py> x = 'b'
                            > py> e1 = enum(lst)
                            > py> v = e1(x)
                            > py> (x in lst) and (e1[v] == x)
                            > True[/color]

                            Yeah, except I actually left out one thing: I also want type(v)==e1.

                            rg

                            Comment

                            • Ron Garret

                              #15
                              Re: __getitem__ method on (meta)classes

                              In article <42370fd0.85723 3857@news.oz.ne t>,
                              bokr@oz.net (Bengt Richter) wrote:
                              [color=blue]
                              > On Mon, 14 Mar 2005 23:44:46 -0700, Steven Bethard <steven.bethard @gmail.com>
                              > wrote:
                              >[color=green]
                              > >Ron Garret wrote:[color=darkred]
                              > >> What I'm really trying to do is to create enumerated types such that if:
                              > >>
                              > >> e1 = enum(lst) and v = e1(x)
                              > >>
                              > >> then
                              > >>
                              > >> (x in lst) and (e1[v] == x)[/color]
                              > >
                              > >Use a class with __call__ and __getitem__:
                              > >
                              > >py> class enum(object):
                              > >... def __init__(self, vals):
                              > >... self.vals = vals
                              > >... def __call__(self, val):
                              > >... return self.vals.index (val)
                              > >... def __getitem__(sel f, index):
                              > >... return self.vals[index]
                              > >...
                              > >py> lst = 'abcd'
                              > >py> x = 'b'
                              > >py> e1 = enum(lst)
                              > >py> v = e1(x)
                              > >py> (x in lst) and (e1[v] == x)
                              > >True[/color]
                              >
                              > For that, why not just
                              >
                              > class enum(list):
                              > def __call__(self, val): return self.index(val)[/color]

                              Because I forgot to mention that I also want type(v)==e1. (Enum is a
                              small part of a static typing system for automatically generating
                              database schema from data models.)

                              rg

                              Comment

                              Working...