0

If everything is an object in python then why does the following not work?

class Hello(object):

    def hello(self):
        print(x)

h = Hello()
h.hello.x = "hello world"

When doing this I get:

AttributeError: 'instancemethod' object has no attribute 'value'

A way which I can achieve this is by using partial however I am not sure what the effects of that would be on an object. Is there another way to achieve this?

from functools import partial
class Hello(object):

    def hello(self, x):
        print(x)

h = Hello()
newMethod = partial(h.hello, "helloworld")
newMethod()

Okay, from the comments below, people want a "real" scenario one example which can be considered as the following:

file.txt contains the following list
["ItemID1", "ItemID2", "ItemID3", "ItemID4"]

def BindMethods(self):
    # ["ItemID1", "ItemID2", "ItemID3", "ItemID4"]
    items = readFromFile("file.txt")
    self.bind(AN_EVENT, GenericMethod)

 def GenericMethod(self, i, event):
     # The person calling this method knows that the method
     # only accepts a single argument, not two.
     # I am attempting to preserve the interface which I do not have control over
     data = generateDataFromID(i)
     table[i] = data

The idea here being that you bind a method to an event whereby the caller is expecting the method of a specific signature i.e. a single input argument.

But at the point of binding you want to pass in some extra information down to the handler. You don't know how much information you want to pass down since it is variable.

A expanded/compile time version of the same thing would be something like this:

file.txt contains the following list
["ItemID1", "ItemID2", "ItemID3", "ItemID4"]

def BindMethods(self):
    # ["ItemID1", "ItemID2", "ItemID3", "ItemID4"]
    items = readFromFile("file.txt")
    self.bind(AN_EVENT, self.Item1Method)
    self.bind(AN_EVENT, self.Item2Method)
    self.bind(AN_EVENT, self.Item3Method)
    ......

 def Item1Method(self, event):
     data = generateDataFromID("Item1")
     table["Item1ID"] = data

 def Item2Method(self, event):
     data = generateDataFromID("Item2")
     table["Item2ID"] = data
Har
  • 3,727
  • 10
  • 41
  • 75
  • Wait, what? Are you trying to bind an attribute to a method which then should print it from its local scope? Can you elaborate more on your code's desired behavior? – jbndlr Oct 11 '16 at 10:54
  • You haven't even instantiated this variable. have you read any tutorial or anything? How do you learn python? – gonczor Oct 11 '16 at 10:55
  • Why don't you tell us what you really are trying to do? Seems like there's an XY problem here. – Ozgur Vatansever Oct 11 '16 at 10:55
  • what is x? You never defined x... so what is the idea? You want to call the code with a string which is then printed? Then define this attribute first... – AnyOneElse Oct 11 '16 at 10:56
  • 3
    Guys, the problem here is twofold and it has nothing to do with `x` not being set yet: 1) Why does `__setattr__` not work with methods like it does with functions? 2) An XY problem because there's probably a better way to do what OP is trying to do. – timgeb Oct 11 '16 at 10:58
  • @timgeb this however would be answered here: http://stackoverflow.com/questions/155609/difference-between-a-method-and-a-function – AnyOneElse Oct 11 '16 at 11:04
  • 1
    @timgeb ...and here too, as mentioned in the answer below: http://stackoverflow.com/questions/7034063/adding-attributes-to-instancemethods-in-python – Marco Lombardi Oct 11 '16 at 11:07

3 Answers3

4

This answer was referring to the question before it was editet; it might not be appropriate anymore.

Actually, there is a solution to this question. In python 3, you can directly assign attributes to bound methods, whereas in python 2.7 it is not that straightforward.

Look at a class definition like this:

class Hello(object):
    def hello(self):
        print(self.hello.x)

In regular use (instantiate and subsequently call the instance method hello()), the implementation would fail due to the missing attribute x:

h = Hello()
h.hello()

>>> AttributeError: 'function' object has no attribute 'x'

Changing the code a bit (Warning: Hack.) to something like this:

class Hello(object):
    def hello(self):
        if not hasattr(self.hello, 'x'):
            self.hello.__dict__['x'] = 0
        print(self.hello.x)

Would let you use the class implementation just as usual:

h = Hello()
h.hello()

>>> 0

print(h.hello.x)

>>> 0

Thus, you can bind attributes to bound methods, but the way you tried it only works in python 3.

Whatever this is good for, you may want to ask yourself whether it is the best way to achieve the desired behavior.

jbndlr
  • 4,965
  • 2
  • 21
  • 31
2

After your edit, this seems to me as if you seek a possibility to route certain events to certain functions, right?

Instead of binding new attributes to existing instance methods, you should think about setting up a clean and extendable event routing system.

A Basic Routing Core

While usually application of the singleton pattern always is a good point to ask oneself whether one is following the right concept, it totally makes sense here. The core of your routing is something similar to this:

def singleton(cls):
    instance = cls()
    instance.__call__ = lambda: instance
    return instance

@singleton
class EventRouter(object):
    def __init__(self):
        self._routes = dict()

    def bind(self, key, func):
        self._routes[key] = func

    def route(self, key):
        if key in self._routes.keys():
            return self._routes[key]()
        else:
            raise KeyError('Unbound routing key: {}'.format(key))

The EventRouter is a singleton class that can bind functions to certain keys and is furthermore able to route occurring events to their desired destination functions.
You now kan bind an event to a desired function during runtime:

def my_event_function():
    print('An event was handled.')

EventRouter.bind('E_MYEVT', my_event_function)

# If you are iterating over events (for example drawn from a file),
# you can direct them to their functions:
for e in ['E_MYEVT', 'E_WHATEVT', 'E_NOEVT']:
    EventRouter.route(e)

A Routing Decorator

If required, you can set up another decorator that can be used to directly bind methods to a certain event type:

def eventroute(key):
    def register_method(call):
        EventRouter.bind(key, call)
    return register_method

@eventroute('E_MYEVT')
def handle_myevt():
    print('Handling event: E_MYEVT')

@eventroute('E_WHATEVT')
def handle_whatevt():
    print('Handling event: E_WHATEVT')

if __name__ == '__main__':
    for e in ['E_MYEVT', 'E_WHATEVT']:
        EventRouter.route(e)

>>> Handling event: E_MYEVT
>>> Handling event: E_WHATEVT

Passing Arguments to the Route Endpoints

If you want to pass arguments to the endpoints, just extend the calls by optional keyword arguments **kwargs when forwarding the call:

In EventRouter class, change route method to:

def route(self, key, **kwargs):
    if key in self._routes.keys():
        return self._routes[key](**kwargs)
    else:
        raise KeyError('Unbound routing key: {}'.format(key))

For your endpoints, set the desired arguments. In this example, I use the argument filename:

@eventroute(...)
def handle_...(filename):
    print('Handling event: ...')
    print(filename)

And when dispatching your events, provide the required argument as kwarg:

events = ['E_MYEVT', 'E_WHATEVT']
for e in events:
    EventRouter.route(e, filename='whatever.jpg')

>>> Handling event: E_MYEVT
>>> whatever.jpg
>>> Handling event: E_WHATEVT
>>> whatever.jpg

Where to go

This is a brief overview of just a very basic setup.
You can extend your EventRouter to be arbitrarily complex, e.g. add routing keys in a multi-layered hierarchy, or the like. Furthermore, you can add more arguments to your methods. You can even think about extending the eventroute decorator to define the kwargs that shall be passed to the endpoint function to keep the function signatures as generic as possible.

Might seem like overkill, but structures like these are pretty neat and can be extended by anyone who imports your library/script and wants to add her own endpoints for individual events.

jbndlr
  • 4,965
  • 2
  • 21
  • 31
-1

The x in the first version is not defined: the way you write the code, it should be a global variable; the way you use it, it should be an attribute of the object. You want to do something like

class Hello(object):
    def __init__(self, x):
        self.x = x

    def hello(self):
        print(self.x)

h = Hello("Hello World")
Marco Lombardi
  • 545
  • 4
  • 18
  • This does not answer why `hello.x = 'value'` works when `hello` is a function, but not a method. – timgeb Oct 11 '16 at 11:02
  • 3
    The answer is actually here: http://stackoverflow.com/questions/7034063/adding-attributes-to-instancemethods-in-python – Marco Lombardi Oct 11 '16 at 11:05