12

I'm wondering if it is possible to use setattr to set an attribute to a method within a class like so because when I try I get an error which is going to be shown after the code:

class Test:
    def getString(self, var):
        setattr(self.getString, "string", var)
        return self.getString
test = Test()
test.getString("myString").string

Which errors AttributeError: 'method' object has no attribute 'string' so I tried it without putting .string and just tried test.getString("myString") Same error, but then I tried it without the using the class just like this

def getString(var):
    setattr(getString, "string", var)
    return getString

getString("myString").string

It returned "myString" like I wanted it to, so how would I do this within a class and why does it work outside of one but inside of one?

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
ruler
  • 613
  • 1
  • 10
  • 17
  • 2
    Why did you use `setattr` for your experiment instead of `self.getString.string = var`? – user2357112 Nov 30 '13 at 21:37
  • Related: [Adding attributes to instancemethods in Python](http://stackoverflow.com/questions/7034063/adding-attributes-to-instancemethods-in-python) – John1024 Nov 30 '13 at 21:38
  • @user2357112 not sure, just personal preference I guess, but it still would give the same error I'm just wondering on how I would set something up like that. – ruler Nov 30 '13 at 21:44

3 Answers3

11

type( test.getString ) is builtins.method and from the documentations ( methods ),

since method attributes are actually stored on the underlying function object (meth.__func__), setting method attributes on bound methods is disallowed. Attempting to set an attribute on a method results in an AttributeError being raised.

There are (at least) two possible solutions depending on which behaviour you are looking for. One is to set the attribute on the class method:

class Test:
    def getString(self, var):
        setattr(Test.getString, "string", var)
        return self.getString

test = Test()
test.getString("myString").string  # > "myString"

test2 = Test()
test2.getString.string # > this is also "myString"

and the other is to use function objects:

class Test:
    class getStringClass:
        def __call__ ( self, var ):
            setattr( self, "string", var )
            return self

    def __init__( self ):
        self.getString = Test.getStringClass( )

test = Test( )
test.getString( "myString" ).string   # > "myString"

test2 = Test()
test2.getString.string  # > this is error, because it does not
                        # have the attribute 'string' yet
behzad.nouri
  • 74,723
  • 18
  • 126
  • 124
4

Functions are like most other objects in that you can freely add attributes to them. Methods, on the other hand... conceptually they're just functions, but they behave slightly differently (implicity pass self) and therefore are implemented with a bit of extra glue around functions.

Every time self.getString is evaluated, a new (bound) method object is created, which is a thin wrapper around the underlying function (which you can access as Test.getString). These method objects don't allow adding attributes, and even if they did, your code would not work because it juggles multiple distinct method objects (though they all wrap the same function).

You can't make this work with bound methods. Since you presumably want the string to be attached to the Test object (indirectly, by being attached to its method), you can make it an attribute of Test. You could even create your own object that behaves like a method but allows attributes (you'd have to explicitly add it in __init__), but honestly there's probably a better way that keeps data and methods separated. If, on the other hand, you want to attach this attribute to the underlying function (which would mean it's shared by all Test instances), you can just set the attribute on Test.getString.

0

So, I found a way but it's not really how I wanted to do it personally. If anyone does find another way to do the following code feel free to comment on how to do it.

class Test:
    def getString(self, string):
        setattr(self,"newString",self)
        self.newString.string = string
    return self.newString

Like I said, I don't feel like I accomplished anything by doing it that way, but it works for what I need and if you do find another way comment below.

ruler
  • 613
  • 1
  • 10
  • 17
  • 1
    This method can be written more simply as `self.string = string; return self`. The `newString` is a self-reference and doesn't buy you anything. –  Nov 30 '13 at 21:56
  • 1
    Yeah, that is true, but I would rather assign one variable if I wanted multiple attributes like if the method accepted multiple objects you could do `test.getString("hello","world").stringOne` or the opposite `test.getString("hello","world").stringTwo` I would rather use one thing but thats just personal preference they would both do the same thing. – ruler Nov 30 '13 at 22:24
  • I don't think you fully understood. With your code, `test.getString("a")` sets (in addition to `test.newString`) the attribute `test.string`. An equivalent two-argument function would add the attributes `newString`, `stringOne` and `stringTwo` to `test`. The only thing my simplification removes is `test.newString`, which is redundant as it's always the same as `test`. –  Nov 30 '13 at 22:40
  • Yeah, I just prefer having it for what I was trying to do not necessarily for my example code though, but anyways I choose a different route of using `setattr(Test.getString,"string",string)` since it eliminates the problem of test.myString.string and test.string doing the same thing it gives an error with the other way. – ruler Nov 30 '13 at 23:40