Francis
Francis

Reputation: 703

Python : Set method attribute from within method

I am trying to make a python decorator that adds attributes to methods of a class so that I can access and modify those attributes from within the method itself. The decorator code is

from types import MethodType
class attribute(object):

    def __init__(self, **attributes):
        self.attributes = attributes

    def __call__(self, function):

        class override(object):

            def __init__(self, function, attributes):
                self.__function = function
                for att in attributes:
                    setattr(self, att, attributes[att])

            def __call__(self, *args, **kwargs):
                return self.__function(*args, **kwargs)

            def __get__(self, instance, owner):
                return MethodType(self, instance, owner) 

        retval = override(function, self.attributes)
        return retval

I tried this decorator on the toy example that follows.

    class bar(object):

        @attribute(a=2)
        def foo(self):
            print self.foo.a
            self.foo.a = 1

Though I am able to access the value of attribute 'a' from within foo(), I can't set it to another value. Indeed, when I call bar().foo(), I get the following AttributeError.

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

Why is this? More importantly how can I achieve my goal?


Edit

Just to be more specific, I am trying to find a simple way to implement static variable that are located within class methods. Continuing from the example above, I would like instantiate b = bar(), call both foo() and doo() methods and then access b.foo.a and b.doo.a later on.

    class bar(object):

        @attribute(a=2)
        def foo(self):
            self.foo.a = 1

        @attribute(a=4)
        def doo(self):
            self.foo.a = 3

Upvotes: 1

Views: 5224

Answers (4)

Francis
Francis

Reputation: 703

Based on other contributors's answers, I came up with the following workaround. First, wrap a dictionnary in a class resolving non-existant attributes to the wrapped dictionnary such as the following code.

class DictWrapper(object):
    def __init__(self, d):
        self.d = d
    def __getattr__(self, key):
        return self.d[key]

Credits to Lucas Jones for this code.

Then implement a addstatic decorator with a statics attribute that will store the static attributes.

class addstatic(object):

    def __init__(self, **statics):
        self.statics = statics

    def __call__(self, function):

        class override(object):

            def __init__(self, function, statics):
                self.__function = function
                self.statics = DictWrapper(statics)

            def __call__(self, *args, **kwargs):
                return self.__function(*args, **kwargs)

            def __get__(self, instance, objtype):
                from types import MethodType
                return MethodType(self, instance)

        retval = override(function, self.statics)
        return retval

The following code is an example of how the addstatic decorator can be used on methods.

   class bar(object):

        @attribute(a=2, b=3)
        def foo(self):
            self.foo.statics.a = 1
            self.foo.statics.b = 2

Then, playing with an instance of the bar class yields :

>>> b = bar()
>>> b.foo.statics.a
2
>>> b.foo.statics.b
3
>>> b.foo()
>>> b.foo.statics.a
3
>>> b.foo.statics.b
5

The reason for using this statics dictionnary follows jsbueno's answer which suggest that what I want would require overloading the dot operator of and instance method wrapping the foo function, which I am not sure is possible. Of course, the method's attribute could be set in self.foo.__dict__, but since it not recommended (as suggested by brainovergrow), I came up with this workaround. I am not certain this would be recommended either and I guess it is up for comments.

Upvotes: 0

kirbyfan64sos
kirbyfan64sos

Reputation: 10727

The best way to do this is to not do it at all.

First of all, there is no need for an attribute decorator; you can just assign it yourself:

class bar(object):
    def foo(self):
        print self.foo.a
        self.foo.a = 1
    foo.a = 2

However, this still encounters the same errors. You need to do:

self.foo.__dict__['a'] = 1

You can instead use a metaclass...but that gets messy quickly.

On the other hand, there are cleaner alternatives.

You can use defaults:

def foo(self, a):
    print a[0]
    a[0] = 2
foo.func_defaults = foo.func_defaults[:-1] + ([2],)

Of course, my preferred way is to avoid this altogether and use a callable class ("functor" in C++ words):

class bar(object):
    def __init__(self):
        self.foo = self.foo_method(self)
    class foo_method(object):
        def __init__(self, bar):
            self.bar = bar
            self.a = 2
        def __call__(self):
            print self.a
            self.a = 1

Or just use classic class attributes:

class bar(object):
    def __init__(self):
        self.a = 1
    def foo(self):
        print self.a
        self.a = 2

If it's that you want to hide a from derived classes, use whatever private attributes are called in Python terminology:

class bar(object):
    def __init__(self):
        self.__a = 1 # this will be implicitly mangled as __bar__a or similar
    def foo(self):
        print self.__a
        self.__a = 2

EDIT: You want static attributes?

class bar(object):
    a = 1
    def foo(self):
        print self.a
        self.a = 2

EDIT 2: If you want static attributes visible to only the current function, you can use PyExt's modify_function:

import pyext

def wrap_mod(*args, **kw):
    def inner(f):
        return pyext.modify_function(f, *args, **kw)
    return inner

class bar(object):
    @wrap_mod(globals={'a': [1]})
    def foo(self):
        print a[0]
        a[0] = 2

It's slightly ugly and hackish. But it works.

My recommendation would be just to use double underscores:

class bar(object):
    __a = 1
    def foo(self):
        print self.__a
        self.__a = 2

Although this is visible to the other functions, it's invisible to anything else (actually, it's there, but it's mangled).

FINAL EDIT: Use this:

import pyext

def wrap_mod(*args, **kw):
    def inner(f):
        return pyext.modify_function(f, *args, **kw)
    return inner

class bar(object):
    @wrap_mod(globals={'a': [1]})
    def foo(self):
        print a[0]
        a[0] = 2
    foo.a = foo.func_globals['a']

b = bar()
b.foo() # prints 1
b.foo() # prints 2
# external access
b.foo.a[0] = 77
b.foo() # prints 77

Upvotes: 7

jsbueno
jsbueno

Reputation: 110456

If you are using Python2 - (and not Python3) - whenever you retrieve a method from an instance, a new instance method object is created which is a wrapper to the original function defined in the class body.

The instance method is a rather transparent proxy to the function - you can retrieve the function's attributes through it, but not set them - that is why setting an item in self.foo.__dict__ works.

Alternatively you can reach the function object itself using: self.foo.im_func - the im_func attribute of instance methods point the underlying function.

Upvotes: 1

brainovergrow
brainovergrow

Reputation: 458

While You can accomplish Your goal by replacing self.foo.a = 1 with self.foo.__dict__['a'] = 1 it is generally not recommended.

Upvotes: 2

Related Questions