简体   繁体   中英

How can I pass an instance binding descriptor as a reference argument to a function?

this here may be a bit tricky with no straight forward solution, but I would also be happy with a more complex solution:

I have an instance binding data descriptor binding to a global instance that I want to pass as a function argument without being evaluated (ie __get__() executed). In this code example it does not work, and the descriptor passes the current value 10 of type int to the function argument, instead of itself:

    """A 'instance binding data descriptor' that sets and returns values
       normally and prints a message logging their access.
       The `x`-value is stored in the instance dictionary `my_instance.__dict__`.
    """
    def __init__(self, init_value=None):
        self.init_value = init_value

    def __get__(self, instance, owner):
        value = getattr(instance, self.__name__ + '_value')
        print('I AM A DATA-DESCRIPTOR retrieving `{}` value: {}'.format(self.__name__, value))
        return value

    def __set__(self, instance, value):
        print('I AM A DESCRIPTOR updating `{}` to value: {}'.format(self.__name__, value))
        setattr(instance, self.__name__ + '_value', value)

class MyClass(object):
    x = RevealAccess(init_value=10)
    def __new__(cls):
        instance = object.__new__(cls)
        for desc_name in [key for key in cls.__dict__.keys() if isinstance(cls.__dict__[key], RevealAccess)]:
            cls.__dict__[desc_name].__name__ = desc_name
            instance.__dict__[desc_name + '_value'] = cls.__dict__[desc_name].init_value
        return instance

my_instance = MyClass()

def func_with_descriptor_as_argument(descriptor_arg):
    print('\n\nINSIDE the function `descriptor_arg=my_instance.x`results in: {}'.format(descriptor_arg))
    print('`type(descriptor_arg)`: {}'.format(type(descriptor_arg)))
    print('Changing `my_instance.x` value results in:')
    descriptor_arg = 5
    print('INSIDE the function after changing `my_instance.x` = {}\n\n'.format(descriptor_arg))

if __name__ == '__main__':

    print('\n\nOUTSIDE the function `my_instance.x`: {}'.format(my_instance.x))
    print('Changing `my_instance.x` value results in:')
    my_instance.x = 5
    print('OUTSIDE the function after changing `my_instance.x` = {}\n\n'.format(my_instance.x))

    print('Reset:')
    my_instance.x = 10

    func_with_descriptor_as_argument(descriptor_arg=my_instance.x)

The output is:

I AM A DATA-DESCRIPTOR retrieving `x` value: 10


OUTSIDE the function `my_instance.x`: 10
Changing `my_instance.x` value results in:
I AM A DESCRIPTOR updating `x` to value: 5
I AM A DATA-DESCRIPTOR retrieving `x` value: 5
OUTSIDE the function after changing `my_instance.x` = 5


Reset:
I AM A DESCRIPTOR updating `x` to value: 10
I AM A DATA-DESCRIPTOR retrieving `x` value: 10


INSIDE the function `descriptor_arg=my_instance.x`results in: 10
`type(descriptor_arg)`: <class 'int'>
Changing `my_instance.x` value results in:
INSIDE the function after changing `my_instance.x` = 5

I do understand that it does not work this way. But what I want is to manipulate the global instance dictionary value my_instance.__dict__['x_value'] inside the function. I have to repeat this with many instances & functions and the actual descriptors are also doing other stuff (in this example it's only printing "I AM..." but in my case it's eg type checking, triggering other processes etc.), so direct dictionary manipulation is undesired. It has to be done by the descriptor.

Question

Can I build some kind descriptor that could pass a kind of reference to the function argument which behaves equivalently?

So far

I've been looking at different options:

  • pass my_instance and the string name x separately or as tuple and work with getattr() , setattr() inside the function. I don't like it because it's for a framework and not nice for anybody.
  • overloading the descriptor, letting it detect if it is passed to a function with eg inspect , some AST -package and and then build an appropriate reference inside the overloaded __get__() and return it. I may manage the detection part, but I have no clue how the reference could look like? Wrap it in another descriptor?

In the end the function argument should work inside the function like a directly callable descriptor but getting/setting the global dictionary my_instance.__dict__['x_value'] (and doing all the other stuff mentioned).

I'm happy for any ideas and looking forward to discuss!

THX in advance!!!

I found a quite nice solution, it's a kind-of-class-wrapper that the descriptor-owner-instance my_instance creates and returns in __getattr__() if you try to get a non-existing attribute from it.

Adjusting above example the solution looks like this:

class RevealAccess(object):
    """A 'instance binding data descriptor' that sets and returns values
       normally and prints a message logging their access.
       The `x`-value is stored in the instance dictionary `my_instance.__dict__`.
    """
    def __init__(self, init_value=None):
        self.init_value = init_value

    def __get__(self, instance, owner):
        value = getattr(instance, self.__name__ + '_value')
        print('I AM A DATA-DESCRIPTOR retrieving `{}` value: {}'.format(self.__name__, value))
        return value

    def __set__(self, instance, value):
        print('I AM A DESCRIPTOR updating `{}` to value: {}'.format(self.__name__, value))
        setattr(instance, self.__name__ + '_value', value)

class DescriptorReference:
    def __init__(self, instance, descriptor_name):
        self.__dict__['instance'] = instance
        self.__dict__['descriptor_name'] = descriptor_name

    def __getattr__(self, name):
        return object.__getattribute__(self.instance, self.descriptor_name)

    def __setattr__(self, dummy, value):
        setattr(self.instance, self.descriptor_name, value)

class MyClass(object):
    x = RevealAccess(init_value=10)
    def __new__(cls):
        instance = object.__new__(cls)
        for desc_name in [key for key in cls.__dict__.keys() if isinstance(cls.__dict__[key], RevealAccess)]:
            cls.__dict__[desc_name].__name__ = desc_name
            instance.__dict__[desc_name + '_value'] = cls.__dict__[desc_name].init_value
        return instance

    def __getattr__(self, name):
        return DescriptorReference(instance=self, descriptor_name=self.__class__.__dict__['x'].__name__)

my_instance = MyClass()

def func_with_descriptor_value_as_argument(descriptor_arg):
    print('\n\nINSIDE the function `descriptor_arg=my_instance.x`results in: {}'.format(descriptor_arg))
    print('`type(descriptor_arg)`: {}'.format(type(descriptor_arg)))
    print('Changeing `my_instance.x` value results in:')
    descriptor_arg = 5
    print('INSIDE the function after changeing `my_instance.x` = {}\n\n'.format(descriptor_arg))

def func_with_descriptor_as_argument(descriptor):
    print('\n\nINSIDE the function `descriptor_arg=my_instance.x`results in: {}'.format(descriptor.x))
    print('`type(descriptor_arg)`: {}'.format(type(descriptor.x)))
    print('Changeing `my_instance.x` value results in:')
    descriptor.x = 5
    print('INSIDE the function after changeing `my_instance.x` = {}\n\n'.format(descriptor.x))

if __name__ == '__main__':

    x_ref =  DescriptorReference(instance=my_instance,
                                 descriptor_name=my_instance.__class__.__dict__['x'].__name__)

    print('\n\nOUTSIDE the function `my_instance.x`: {}'.format(my_instance.x))
    print('Changeing `my_instance.x` value results in:')
    my_instance.x = 5
    print('OUTSIDE the function after changeing `my_instance.x` = {}\n\n'.format(my_instance.x))

    print('Reset:')
    my_instance.x = 10

    func_with_descriptor_as_argument(descriptor=my_instance.x_ref)

    print('OUTSIDE the function after changeing INSIDE the function `my_instance.x` = {}\n\n'.format(my_instance.x))

Now normally you call my_instance.x to run the descriptor and get/set the value of it's global dictionary entry my_instance.__dict__['x'] and do all the other descriptor stuff/work. By passing my_instance with a non-existing attribute call as argument to the function, eg func_...(descriptor=my_instance.x_ref) , my_instance calls __getattr_() after not finding the x_ref in its __dict__ . Then __getattr__() generates the DecriptorReference() and return it to descriptor argument in the function. Inside the function you have now full descriptor functionality which is manipulating the global dictionary of my_instance . So the call descriptor.x inside the function does absolutely the same as my_instance.x outside the function.

Note: In this implementation you can call any attribute on descriptor and it will be identical to my_instance.x , eg descriptor.made_my_day . If this is not desired it can be easily changed with a if name == 'x': before the return in DescriptorReference.__getattr__()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM