1

How to call a method using getattr? I want to create a metaclass, which can call non-existing methods of some other class that start with the word 'oposite_'. The method should have the same number of arguments, but to return the opposite result.

def oposite(func):
    return lambda s, *args, **kw:  not oposite(s, *args, **kw)


class Negate(type):
    def __getattr__(self, name):
        if name.startswith('oposite_'):
            return oposite(self.__getattr__(name[8:]))        
    def __init__(self,*args,**kwargs):
        self.__getattr__ = Negate.__getattr__


class P(metaclass=Negate):
    def yep(self):
        return True
    def maybe(self, sth):
        return sth

But the problem is that

self.__getattr__(sth) 

returns a NoneType object.

>>> p = P()
>>> p.oposite_yep()    #should be False
Traceback (most recent call last):
  File "<pyshell#115>", line 1, in <module>
    p.oposite_yep()
TypeError: <lambda>() takes at least 1 positional argument (0 given)
>>> p.oposite_maybe(False)    #should be true

How to deal with this?

1
  • 8
    For the record, "oposite" is spelled with two p's. But, as a colleague of mine used to say, "Programmers don't need to spell correctly, they need to spell consistently." Commented May 27, 2010 at 0:05

3 Answers 3

2
class Negate(type):
    def __getattr__(self, name):
        if name.startswith('oposite_'):
            def oposite(*args,**kwargs):
                return not getattr(self,name[8:])(*args,**kwargs)
            return oposite
        else:
            raise AttributeError("%r object has no attribute %r" %
                                 (type(self).__name__, name))

    def __init__(self,*args,**kwargs):
        self.__getattr__ = Negate.__getattr__

class P(metaclass=Negate):
    def yep(self):
        return True
    def same(self,x,y):
        return x==y

p=P()
print(p.oposite_yep())
# False
print(p.oposite_same(1,2))
# True
print(p.nope())

By the way, you can also do this with a class decorator instead of a metaclass:

def Negater(cls):
    def __getattr__(self, name):
        if name.startswith('oposite_'):
            def oposite(*args,**kwargs):
                return not getattr(self,name[8:])(*args,**kwargs)
            return oposite
        else:
            raise AttributeError("%r object has no attribute %r" %
                                 (type(self).__name__, name))
    setattr(cls,'__getattr__',__getattr__)
    return cls

@Negater
class P():
    ....
Sign up to request clarification or add additional context in comments.

1 Comment

ok, but that is if yep has no args except of self. If yep takes another argument, it doesn't work.
0

You forgot to handle the case where the attribute name doesn't start with 'oposite_'.

1 Comment

It's just one return statement more. But even with it, the problem remains.
0

The error is "TypeError: <lambda>() takes at least 1 positional argument (0 given) "

This has nothing to do with you metaclass magic, it's because you don't pass any argument to the lambda function.

When you do this:

p.oposite_yep

It call __getattr__, which returns the result of opposite(), which is a lambda function.

Your lamda function is returned, it is created on the fly, and never bouned to the instance. It won't receive 'self' as a first argument: it's just an anonymous function returned on the fly.

So when you do this:

p.oposite_yep()

You basically call the lambda, without any argument, which cause the error.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.