Partial evaluation of a Python class method based on where it is available

I have what is essentially the following in python:

class X(object): pass class Y(object): @classmethod def method(cls, x_inst, *args, **kwargs): #do some work here that requires an instance of x pass 

What I would like to do is add a dynamic property to all X instances, allowing acces to Y , which implicitly populates the first method parameter with this instance. EG I would like the following code to work the same way:

 # current x = X() result = Y.method(x, 1, 2, 3) # desired x = X() xYmethod(1, 2, 3) 

There are several methods in several subclasses for which I would like to implement this behavior. I am currently creating a YProxy class that X actually returns, and then puts part of the code into it. It seems to be quite complicated and hard to maintain:

 class X(object): @property def Y(self): return YProxy(self) class Y(object): @classmethod def method(cls, x_inst, *args, **kwargs): #do some work here that requires an instance of x pass class YProxy(object): def __init__(self, x_inst): self.x_inst = x_inst def method(self, *args, **kwargs): return Y.method(self.x_inst, *args, **kwargs) 

Is there a way to conditionally partially evaluate class methods for an object?

+6
source share
5 answers

This can be done using the Descriptor object + declaration of the container class explicitly declaring the classes that you need to wrap in this way in your target class.

A descriptor object is any object that defines the __get__ method, which allows you to configure attribute retrieval when the descriptor is part of a class. In this case, we want this attribute, which is the class "Y", to be retrieved from the instance, whenever the method is retrieved from this class, the instance is inserted into the parameter list.

This requires that the resulting attribute itself be a "proxy class" with access to user attributes to allow dynamic packaging to take note.

Having translated all this into Python, we have:

 import types from functools import partial class APIWrapper(object): def __init__(self, apicls, instance): self._apicls = apicls self._instance = instance def __getattribute__(self, attr): apicls = object.__getattribute__(self, "_apicls") instance = object.__getattribute__(self,"_instance") obj = getattr(apicls, attr) if isinstance(obj, types.MethodType): return partial(obj,instance) return obj class APIProperty(object): def __init__(self, cls): self.cls = cls def __get__(self, instance, cls): return APIWrapper(self.cls, instance) class Y(object): @classmethod def method(cls, x, *args): print cls, x, args class X(object): Y = APIProperty(Y) #Example usage: x = X() xYmethod(1,2,3) 

(prints <class '__main__.Y'> <__main__.X object at 0x18ad090> (1, 2, 3) at startup)

But I suppose you do not want to write

 Y = APIWrapper(Y) 

for each of the classes you want to wrap in this way. (And they have these classes defined after the wrapped class, so Y has already been analyzed when analyzing the body of X).

This can be done using metaclasses, class decorators that must be defined for each class that you want to apply to the methods - instead, I created a function that should be called at the end of the module definition, where you define your class “X” - this function will add the required classes as attributes for each specific class (in my example, I want the class to be marked with the attribute "auto_api", but it will come in handy) Thus, the function "auto_api", the definition of the classes X and Y becomes the same ( using the same APIProperty and APIWrapper as above)

 def auto_api(api_classes, glob_dict): for key, value in glob_dict.items(): if isinstance(value, type) and hasattr(value, "auto_api"): for api_class in api_classes: setattr(value, api_class.__name__, APIProperty(api_class)) class X(object): auto_api = True class Y(object): @classmethod def method(cls, x, *args): print cls, x, args auto_api((Y,), globals()) #Example x = X() xYmethod(1,2,3) 
+2
source

Not sure if this is what you want, but some kind of introspection / varname discipline might find you somewhere:

 import functools class X(object): @property def Y(self): return YProxy(self) class Y(object): @classmethod def method(cls, x_inst, *a): print x_inst, a instancemethod = type(Y.method) class YProxy(object): def __init__(self, x_inst): self.x_inst = x_inst def __getattr__(self, k): obj = getattr(Y, k) if isinstance(obj, instancemethod) and obj.func_code.co_varnames[0] == 'cls': return functools.partial(obj, self.x_inst) return obj 

Using cls , as I believe that the first argument name for a class method is a common style. self does not fit.

ETA: you can also check the variable name "x_inst" or something similar if there are other methods of the class, or use the decorator in the corresponding classes to set the property in the methods and c. The point is that you can do what you need in __getattr__ if you have a programmatic way to determine which methods should be provided with an instance and which should not.

+2
source
 import inspect def callthing(): my_caller = inspect.stack()[1] args, varargs, keywords, locals = inspect.getargvalues(my_caller) print args[0] 

When the above callthing , it will receive the arguments of its caller ( [1] "stack stack above current"), printing the first. You can even use named arguments to "get the value of an argument named x_inst ". Perhaps @classmethod should return a function enclosed in a call, for example, callthing ?

You can also do the branching in the decorator itself using inspect.getargspec(my_function) , bearing in mind that the actual value specified by the argument is not available at this point, since the function is built, not called.

+1
source

Why do you bind classes as follows? Of course,

 >>> class X(object): ... def __init__(self, y): ... self.y = y ... ... def method(self, *args, **kwargs): ... self.y.method(self, *args, **kwargs) >>> class Y(object): ... @classmethod ... def method(cls, x, *args): ... print cls, x, args ... >>> y = Y() >>> x = X(y) >>> x.method(Ellipsis) <class '__main__.Y'> <__main__.X object at 0xb727612c> (Ellipsis,) 

will work fine?


What you asked for is somewhat controversial. If xY indeed an object Y class, then of course it does not have this automatic argument binding property that you want. Therefore, xY should be a kind of proxy object that binds you.

But Python already does just that, for example, class methods! If you have an instance of X and you call a method on it, this method will receive self as the first argument; do you know that. So, of course, does it make sense to use this to bind arguments?

0
source

Is it possible to do something like this?

 class X(object): def __init__(self): self.Y = Y(self) class Y(object): def __init__(self, x): self.x = x def method1(self, *args, **kwargs): pass x = X() xYmethod1() 
0
source

Source: https://habr.com/ru/post/906568/


All Articles