Home > Enterprise >  Usage of __setattr__ to rewrite whole method of library class issue: missing 1 required positional a
Usage of __setattr__ to rewrite whole method of library class issue: missing 1 required positional a

Time:05-20

I've got some imported packages with tricky structure and need to call some method that bases on lots of other methods with non-default parameters, which are not class attributes themself like pipeline in sklearn.

Minimal example of this module structure:

class Library_class:
    def __init__(
        self,
        defined_class_options,
      ):
        self.defined_class_options = defined_class_options
        
    def method1( self , default_non_class_arg = 12 ):
        
        assert self.defined_class_options==3
        return default_non_class_arg
        
    def method2( self, image ):
        return image/ self.method1()

Default usage:

    class_instance = Library_class( 3 )
    class_instance.method2( 36 )
> 3.0

I need to set default_non_class_arg to 6 for example.

I've tried multiple approaches:

  1. Analogous to https://stackoverflow.com/a/35634198/7607734
    class_instance.method2( 36 ,
                 method1__default_non_class_arg=3  )

TypeError: method2() got an unexpected keyword argument 'method1__default_non_class_arg'

It don't work probably because class definitely don't have set_params

  1. With setattr on redefined function
    class_instance.__setattr__('method1',Library_class.new_method1)
    class_instance.method2( 36 )

TypeError: new_method1() missing 1 required positional argument: 'self'

CodePudding user response:

Both your snippets and question are quite messy, almost to the point of being unreadable.

Anyway, if you wantt to replace method1 with another function, say new_method1 in an specific instance, just do that. Your call to .__setattr__ does that, but it is not needed at all, (and if it was, due to you not having the method to be replaced name at code writting time, and needed it as a parameter, it is more correct to call the built-in setattr, not the instance method: `setattr(class_instance, "method1", new_method1").

Ordinarily, if you know, at code writting time you have to replace "method1" in an instance, the assigment operator will do it:

class_instance.method1 = new_method1

What went wrong in your examle is that if you assign a method to an instance, instead of a class, you are bypassing the mechanism that Python uses to insert the self attribute into it - so your new_method1 needs a different signature. (and this is exactly what the error message "TypeError: new_method1() missing 1 required positional argument: 'self'" is saying):

class  MyClass:
    ...
    def method1(self, param1=36):
         ...
    ...

def new_method1(param1=6):   # <-- written outside of any class body, sans self
    ...

my_instance = MyClass()
my_instance.method1 = new_method1 

this will work. new_method1 could be written in a class body as well, and could be replaced just the same, but you would have to write it without the self parameter the same, and then it would not work straight as a normal method.

OR, you can, at assigment time, insert the self argument yourself - the functools.partial call is a convenient way to do that:

class MyClass: ... def method1(self, param1=36): ...

def new_method1(self, param1=6):  
     ...
...

my_instance = MyClass()

from functools import partial MyClass.method1 = partial(MyClass.new_method1, my_instance)


Now, this should answer what you are asking, but it would not be honest of me to end the answer without saying this is not a good design. The best thing there is to pull your parameter from another place, it might be from an instance attribute, instead of replacing the method entirely just to change it.

Since for normal attributes, Python will read the class attribute if no instance attribute exists, it will happen naturally, and all you have to do is to set the new default value in your instance.

class  MyClass:
    default_param_1 = 36  # Class attribute. Valid for every instance unless overriden
    ...
    def method1(self, param1=None):
         if param1 is None:
               param1 = self.default_param_1  #Automatically fetched from the class if not set on the instance
         ...
    ...


my_instance = MyClass()
my_instance.default_param_1 = 6
...

  • Related