Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python setattr() to function takes initial function name

I do understand how setattr() works in python, but my question is when i try to dynamically set an attribute and give it an unbound function as a value, so the attribute is a callable, the attribute ends up taking the name of the unbound function when i call attr.__name__ instead of the name of the attribute.

Here's an example:

I have a Filter class:

class Filter:
    def __init__(self, column=['poi_id', 'tp.event'], access=['con', 'don']):
        self.column = column
        self.access = access
        self.accessor_column = dict(zip(self.access, self.column))
        self.set_conditions()

    def condition(self, name):
        # i want to be able to get the name of the dynamically set 
        # function and check `self.accessor_column` for a value, but when
        # i do `setattr(self, 'accessor', self.condition)`, the function 
        # name is always set to `condition` rather than `accessor`
        return name

    def set_conditions(self):
        mapping = list(zip(self.column, self.access))
        for i in mapping:
            poi_column = i[0]
            accessor = i[1]
            setattr(self, accessor, self.condition)

In the class above, the set_conditions function dynamically set attributes (con and don) of the Filter class and assigns them a callable, but they retain the initial name of the function.

When i run this:

>>> f = Filter()
>>> print(f.con('linux'))
>>> print(f.con.__name__)

Expected:

  • linux
  • con (which should be the name of the dynamically set attribute)

I get:

  • linux
  • condition (name of the value (unbound self.condition) of the attribute)

But i expect f.con.__name__ to return the name of the attribute (con) and not the name of the unbound function (condition) assigned to it.

Can someone please explain to me why this behaviour is such and how can i go around it?

Thanks.

like image 650
nosahama Avatar asked Mar 19 '19 12:03

nosahama


People also ask

What is Setattr () used for in Python?

The setattr() function sets the value of the specified attribute of the specified object.

How does __ Setattr __ work in Python?

Python's setattr function accepts an object (the object we're adding the attribute to), a string representing an attribute name, and a value to assign. In each iteration of our for loop, we're assigning a new attribute on our Row object (remember self points to our class instance).

What is Setattr () and Getattr () used for?

Python setattr() and getattr() goes hand-in-hand. As we have already seen what getattr() does; The setattr() function is used to assign a new value to an object/instance attribute.

What will happen if the attribute is not found in Setattr () method?

Example 2: When the attribute is not found in setattr() If the attribute is not found, setattr() creates a new attribute an assigns value to it.


1 Answers

function.__name__ is the name under which the function has been initially defined, it has nothing to do with the name under which it is accessed. Actually, the whole point of function.__name__ is to correctly identify the function whatever name is used to access it. You definitly want to read this for more on what Python's "names" are.

One of the possible solutions here is replace the static definition of condition with a closure:

class Filter(object):
    def __init__(self, column=['poi_id', 'tp.event'], access=['con', 'don']):
        self.column = column
        self.access = access
        self.accessor_column = dict(zip(self.access, self.column))
        self.set_conditions()

    def set_conditions(self):
        mapping = list(zip(self.column, self.access))
        for column_name, accessor_name in mapping:
            def accessor(name):
                print("in {}.accessor '{}' for column '{}'".format(self, accessor_name, column_name))
                return name

            # this is now technically useless but helps with inspection
            accessor.__name__ = accessor_name
            setattr(self, accessor_name, accessor)

As a side note (totally unrelated but I thought you may want to know this), using mutable objects as function arguments defaults is one of the most infamous Python gotchas and may yield totally unexpected results, ie:

>>> f1 = Filter()
>>> f2 = Filter()
>>> f1.column
['poi_id', 'tp.event']
>>> f2.column
['poi_id', 'tp.event']
>>> f2.column.append("WTF")
>>> f1.column
['poi_id', 'tp.event', 'WTF']

EDIT:

thank you for your answer, but it doesn't touch my issue here. My problem is not how functions are named or defined, my problem it that when i use setattr() and i set an attribute and i give it a function as it's value, i can access the value and perform what the value does, but since it's a function, why doesn't it return it's name as the function name

Because as I already explained above, the function's __name__ attribute and the name of the Filter instance attribute(s) refering to this function are totally unrelated, and the function knows absolutely nothing about the names of variables or attributes that reference it, as explained in the reference article I linked to.

Actually the fact that the object you're passing to setattr is a function is totally irrelevant, from the object's POV it's just a name and an object, period. And actually the fact you're binding this object (function or just whatever object) to an instance attribute (whether directly or using setattr(), it works just the same) instead of a plain variable is also totally irrelevant - none of those operation will have any impact on the object that is bound (except for increasing it's ref counter but that's a CPython implementation detail - other implementations may implement garbage collection diffently).

like image 180
bruno desthuilliers Avatar answered Oct 21 '22 12:10

bruno desthuilliers