1

In a specific class I built which essentially acts as a Wrapper for a user-defined function, a user would only be using 2-3 main functions from my class aside from instantiation. I attempted to make my class decorator compatible so that the user could simply build his function and use the decorator instead of explicitly calling 2-3 functions from the class I built. I am now trying to test this.

Main:

from class_I_built import ClassIBuilt

def main():
    testfcn()


@ClassIBuilt('1', MessageConverter, timeout=10)
def testfcn():
    count = 0
    while count < 1000:
        count = count + 1

main()

class_I_built

import socket
from apb import APB
import config
import time

MCAST_GRP = config.MCAST_GRP
MCAST_PORT = config.MCAST_PORT


class ClassIBuilt(APB):
    processvar = 0

    def __init__(self, id, message_protocol, timeout=config.TIMEOUT):

        self.sock = 1

        self.p_id = p_id
        self.timeout = timeout
        self.message_protocol = message_protocol

    def __call__(self, decorator_function=1):
        self.publish_start('starting message')
        decorator_function(*args, **kwargs)
        self.publish_end('ending message')

    def connect(self):
        ttl = 2
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)

    def publish(self, message_type='alert', message=''):


        self.connect()
        s = self.message_protocol(message)
        try:
            self.sock.sendto(s, (MCAST_GRP, MCAST_PORT))
        finally:
            self.sock.close()

    def publish_start(self, message=''):
        print 'publishing start'
        self.publish("start", message)

    def publish_end(self, message=''):
        print 'publishing end'
        self.publish('end', message)

If I don't call testfcn() in main(), then the __call__ method is called and the start and end messages are published as normal. It should not work this way though, since it should only run testfcn() if I call testfcn() explicitly in main(). However, if I call testfcn() in main(), as shown above, then it will send the messages but I get a NoneType object is not callable error.

Output/stack trace:

publishing start
publishing end
Traceback (most recent call last):
  File "Main.py", line 13, in <module>
    main()
  File "Main.py", line 4, in main
    testfcn()
TypeError: 'NoneType' object is not callable

Why is the error occurring, and how can I structure my code so that it only runs if the function is called explicitly in main?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Jeremy Fisher
  • 2,510
  • 7
  • 30
  • 59

1 Answers1

3

Your decorator line creates an instance of your class:

@ClassIBuilt('1', MessageConverter, timeout=10)

That instance is then used to do the actual decorating; you in effect do this:

decorator = ClassIBuilt('1', MessageConverter, timeout=10)
def testfcn(): # ...
testfcn = decorator(testfcn)

Because decorator is an instance of your class, the decorator(testfcn) call is handled by the ClassIBuilt.__call__() method, and that method returns nothing:

def __call__(self, decorator_function=1):
    self.publish_start('starting message')
    decorator_function(*args, **kwargs)
    self.publish_end('ending message')

No return statement means None is returned, so testfcn is set to None. You cannot call None, resulting in the exception you see.

Your __call__ should instead return a wrapper:

def __call__(self, decorator_function=1):
    def wrapper(*args, **kwargs):
        self.publish_start('starting message')
        decorator_function(*args, **kwargs)
        self.publish_end('ending message')
    return wrapper

Now testfcn is bound to the wrapper() function, which then calls the original decorated function.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Thanks. This seemed to fix both errors, but could you possibly explain why the testfcn = decorator(testfcn) line was still running even when it wasn't called in main? – Jeremy Fisher Jul 01 '15 at 14:20
  • @JeremyFisher because *that's what the `@` syntax does*. – jonrsharpe Jul 01 '15 at 14:21
  • @JeremyFisher: I was illustrating what the decorator syntax *does*. The very act of putting the decorator before the `testfcn` function definition runs the decorator. – Martijn Pieters Jul 01 '15 at 14:21