7

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.

nosahama
  • 150
  • 10

2 Answers2

2

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).

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Hi Bruno, 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, i'd look into `setattr`. Also concerning using mutable objects as function args, i don't plan on updating/changing (appending) to these arguments, so i saw no harm here. Thank you so much again. – nosahama Mar 19 '19 at 15:22
  • @EmmanuelEvbuomwan cf my edited answer - TL;DR : I __did__ answer your question already. – bruno desthuilliers Mar 19 '19 at 16:14
0

May I suggest you this :

from types import SimpleNamespace

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 set_conditions(self):
        for i in self.access:
            setattr(self, i, SimpleNamespace(name=i, func=lambda name: name))

f = Filter()
print(f.con.func('linux'))
>>> linux
print(f.con.name)
>>> con

[edited after bruno desthuilliers's comment.]

T. Gwen
  • 104
  • 5
  • 2
    Python has no pointer. `f.con` is actually an expression that evaluates as `getattr(f, "con")`, which in turn will evaluate to `f.__dict__["con"]`, which is a reference to the `method` instance created when evaluating `self.condition`, which method holds references to `f`, `Filter` and `Filter.__dict__["condition"]`. – bruno desthuilliers Mar 19 '19 at 13:39