This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: len() on class broken
Type: Stage:
Components: Interpreter Core Versions: Python 2.4
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: arigo, georg.brandl, kquick, rhettinger
Priority: normal Keywords:

Created on 2005-12-16 20:18 by kquick, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Messages (6)
msg27093 - (view) Author: Kevin Quick (kquick) Date: 2005-12-16 20:18
With the following python input:

class A:
    @classmethod
    def __len__(cls):
        return 12

print '1',A.__len__()
print '2',A().__len__()
print '3',len(A())
print '4',len(A)

The output always breaks for '4' with 'TypeError: len 
of unsized object'

Same result for @staticmethod or normal instance method 
declaration.
msg27094 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2005-12-16 21:43
Logged In: YES 
user_id=1188172

You want to use a metaclass:

class meta(type):
    def __len__(cls):
        return 12

class B:
    __metaclass__ = B

print len(B)
msg27095 - (view) Author: Kevin Quick (kquick) Date: 2005-12-16 22:52
Logged In: YES 
user_id=6133

That would indeed solve '4', but has a non-orthogonality of 
attribute propagation that I don't understand and which 
seems inconsistent.

In my original:
  '1' demonstrates that __len__ is indeed in the dictionary
      for the class.
  '2' shows the expected attribute propagation effects: if
      at attribute is not found in the instance dictionary,
      the class dictionary is checked.
  '3' shows that len is implemented (generally) by looking
      for a __len__ method on the object in question.
  '4' confuses me, because it means that '3' isn't quite
      correct...

With your metaclass solution (using "__metaclass__ = meta" 
:) it does indeed make '4' work, and '1', but '2' and '3' 
now do not work, showing that instance-->class propagation
does not follow instance-->class-->metaclass.  Or something
...

Approaching this a different way:

My understanding of @classmethod (or perhaps more properly
@staticmethod) is that it allows "constant" methods that are
independent of the particular object instance.  So if I had:

class C:
  @staticmethod
  def f(): return 1

then both C.f() and C().f() are valid and return 1.

Why the len() translation to __len__ works *differently*
is strange.  I'm still defining a method, just one that
I want Python to use for any len(A) or len(A()) refs,
translating those to A.__len__() or A().__len__() and using 
those just as for C and f, respectively.
msg27096 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2005-12-17 04:40
Logged In: YES 
user_id=80475

Guido, this issue arises throughout the language in various
guises (for instance, it applies to __neg__ as well as
__len__).  It comes-up whenever built-in functions or
operators bypass attribute lookup in favor of direct slot
access.  

Much of the code in abstract.c is in the form:

   def PyObject_SomeFunc(o):
        if slots.somefunc is not None:
            return slots.somefunc(o)
        raise TypeError

If we cared about this (and I'm not sure we do), the
solution is to make the abstract.c functions smarter:

   def PyObject_SomeFunc(o):
        if slots.somefunc is not None:
            return slots.somefunc(o)
        try:
            f = gettattr(o, 'somefunc')
        except AttributeError:
            raise TypeError
        else:
            return f()

The advantage of the change is restoring the symmetry
between len(o) and o.__len__() where the method definition
is available via attribute lookup but not via a slot.  The
thought is to keep the speedy access as default, but if that
fails, then do a normal attribute lookup before barfing back
an error message.  This is precedent for this solution
elsewhere in the codebase (though I don't remember where at
the moment).  OTOH, I'm not sure we care.





 pervades the code in abstract.c which is in the form:

   def PyObject_Size
msg27097 - (view) Author: Kevin Quick (kquick) Date: 2005-12-19 15:25
Logged In: YES 
user_id=6133

This bug *may* be related to Bug#1066490
msg27098 - (view) Author: Armin Rigo (arigo) * (Python committer) Date: 2005-12-26 17:27
Logged In: YES 
user_id=4771

Un-assigning Guido.

This is a known documentation bug: all this is expected, but
under-documented.  Indeed, len(x) calls the special method
__len__ of 'x', but that's quite different from the
expression "x.__len__()".   Indeed, the real definition of
"calling a special method" on an object 'x' is to look up
the name "__len__" in the dict of type(x), then in the dict
of the parent types in MRO order.  It's really not the same
thing as an attribute lookup.

This definition makes sense for the Python language; it has
nothing to do with the C slots.  The reason is more
fundamental.  Consider, say, what would occur if "repr(x)"
was equivalent to "x.__repr__()":

>>> class X(object):
...    def __repr__(self):
...        return "hello"
...
>>> X()
hello
>>> X
TypeError: __repr__() takes exactly 1 argument (0 given)

because X.__repr__() is just an unbound method call with a
missing argument.
History
Date User Action Args
2022-04-11 14:56:14adminsetgithub: 42704
2005-12-16 20:18:24kquickcreate