简体   繁体   中英

__setattr__ function in Python

I am trying to understand the concept of delegation in Python by using the getattr and setattr function . By basic idea is to first set the value of the attribute 'lang' in the Person class through the class Professional and then retrieve the same. The problem is that the result is an infinite loop.

class Person:
    def __init__(self,name='Subhayan',job='Engineer',unique='Unique'):
        print ("Inside init function of Person")
        self.name = name
        self.job = job
        self.salary = 50000
        self.lang = "Perl"

    def __setattr__(self,att,value):
        self.__dict__[att] = value


class Professional:
    job = 'Engineer'
    salary = 75000
    def __init__(self):
        print ("Inside Professional init function")
        self.person = Person()

    def __getattr__(self,attr):
        print ("Calling the getattr function")
        return getattr(self.person, attr)

    def __setattr__(self,att,value):
        # print ("calling the setattr function setting the value of %s to %s" %(attr,value))
        self.person.__setattr__(self,att,value)


if __name__ == '__main__':
    print ("Calling the script as main")
    Prof = Professional()
    print ("Salary is",Prof.salary)
    print ("name is",Prof.__class__)
    print ("Setting the value of lang")
    Prof.lang = 'Python'
    value = Prof.lang
    print ("The value of lang is ;",value)

__setattr__ is called for all attribute setting. This includes the self.person = Person() call in __init__ :

def __init__(self):
    print ("Inside Professional init function")
    self.person = Person()

This'll call self.__setattr__('person', Person()) , which in turn tries to access self.person , which then calls self.__getattr__('person') because there is no such attribute yet. And in __getattr__ you then end up in an infinite loop as you try to continually access self.person .

You could test for the specific person attribute in __setattr__ (and delegate that to the base implementation):

def __setattr__(self, att, value):
    # print ("calling the setattr function setting the value of %s to %s" %(attr,value))
    if att == 'person':
        return super().__setattr__(att, value)
    return self.person.__setattr__(self,att,value)

You probably also want to add a test in __getattr__ ; if it is called with person , then that attribute hasn't been set yet and an AttributeError should be raised:

def __getattr__(self,attr):
    print ("Calling the getattr function")
    if attr == 'person':
        raise AttributeError('person')
    return getattr(self.person, attr)

The __setattr__ and __getattr__ also apply before your instance is fully initialized. In this case, your line self.person = Person() , calls __setattr__ . This then calls __getattr__ (because self.person is not yet defined), which then recursively calls __getattr__ again (for the same reason).

There are a few ways around this. Perhaps the simplest is to circumvent the __setattr__ call on the initial self.person assignment by doing, for instance super().__setattr__('person', Person()) .

In general, you need to be careful when using these methods, since they may be called more often than you realize. If your special handling applies to only a few certain attributes, you may want to use a property instead.

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