R.O.S.S
R.O.S.S

Reputation: 625

Magic methods using lambdas

I'm trying to create object dynamically using type. Specifically, I want to create a float which has a len.

This is what I tried:

_float = type('_float', (float,), {})

Another file, where's the instance created using function (constructor) reference:

obj = x[1](x[0])  # `x` is a 2-length tuple: (value, function)

obj.old = x[0]
obj.__len__ = lambda: len(x[0])  # I'm not sure if this lambda should take argument 'self'

out.append(obj)  # 'out' is a list of output

This code is in loop, and when it's done, I run following (for testing where's the bug):

print(out[0].old)  # => 1, correct
print(out[0].__len__)  # <function bla.bla.<lambda> at: 0xblabla>
print(out[0].__len__())  # 1, correct (number of digits)
print(len(out[0]))  # TypeError: object of type '_float' has no len()

The basic idea is that the user imports my module, gives it a str value and a function. My module's object then applies that function to the str value and returns a resulting object. However, it must keep the original value or at least its __len__.

Upvotes: 1

Views: 309

Answers (3)

Matthew
Matthew

Reputation: 7590

This is one of the differences between python 3 and python 2. This would work in python 2 (using "old style" classes), but fails in python 3.

For example, in python 2 the following works:

class test: pass # "old style" class
a = test()
a.__len__ = lambda: 10
len(a) # 10

but this doesn't

class test(object): pass # inherit from object, "new style" class
a = test()
a.__len__ = lambda self: 10
len(a) # TypeError: object of type 'test' has no len()

and it doesn't work in python 3 as all classes are "new style".

Essentially, in new style classes anything that uses a built in overloaded method (methods with double underscores on both sides) will bypass the instance altogether, and go straight to the class.

As long as you define the method on the class, it works fine. For example (python 3)

class test: pass
test.__len__ = lambda self: 10
a = test()
len(a) # 10

In fact, you can even create the method after instance creation

class test: pass
a = test()
a.__class__.__len__ = lambda self: 10
len(a) # 10

A search for "python new style classes method lookup" should give you more information than you could ever want on this. Additionally, chapters 32 and 38 of Learning Python, 5th edition (Oreilly, 2013) discusses these differences in depth.

Upvotes: 1

viraptor
viraptor

Reputation: 34205

Maybe this is enough:

class _float(float):
    def __len__(self):
        return len(str(self))

Doesn't use dynamic type.

Upvotes: 0

Wombatz
Wombatz

Reputation: 5458

The reason why it does not work is explained here

TL;DR You are adding the __len__ method to the instance dict and not to the class itself.

Try this:

_float.__len__ = lambda self: YOUR_LEN

Then this works:

print(len(_float(1)))

But as the comments suggest subclassing float by defining a class the normal way is somewhat less hacky.

Edit:

If you want your __len__ method to return the length of the old attribute you can do it like this:

_float.__len__ = lambda self: self.old

This might fail if len is called on an object without an old attribute

AttributeError: '_float' object has no attribute 'old'

Upvotes: 1

Related Questions