简体   繁体   中英

decorator argument: unable to access instance attribute

Context

class MyMockClient below has two methods:

  • push_log : pushes one log from list self.logs and pops it
  • push_event : pushes one event from list self.events and pops it

Both methods are decorated using __iterate_pushes which essentially iterate push_event and push_log until their respective lists are entirely popped AND in the full program applies some extra logics (eg tracks how many objects were pushed).

Problem

The decorator __iterate_pushes needs to access self.logs and self.events . However, it does not seem to work. In the example below, you can see that the program fails because self.logs is not available when the function is defined.

Any idea how to solve this?

## Example

class MyMockClient:
    def __init__(self, logs, events):
        self.logs = logs
        self.events = events

    def __iterate_pushes(self, obj):
        """
        Decorate function to iterate the 'pushes' until
        the lists are entirely popped.
        """
        def outer_wrapper(self, decorated_func):
            def inner_wrapper(self):
                responses = []
                print(f"len: {len(obj)}")
                while len(obj) > 0: # iterate until list is entirely popped
                    response = decorated_func() # when executed, it pops the first item
                    responses.append(response)
                    print(f"len: {len(obj)}")
                return responses

            return inner_wrapper

        return outer_wrapper
        
    @__iterate_pushes(obj = self.logs)
    def push_log(self):
        # Send first log and pops it if successful
        log = self.logs[0]
        print(log, "some extra logics for logs")
        response = "OK" # let's assume API call is OK
        if response == "OK":
            self.logs.pop(0)
        return response
    
    @__iterate_pushes(obj = self.events)    
    def push_event(self):
        # Send first event and pops it if successful
        event = self.events[0]
        print(event, "some extra logics for events")
        response = "OK" # let's assume API call is OK
        if response == "OK":
            self.events.pop(0)
        return response

my_mock_client = MyMockClient(
    logs = [1, 2, 3],
    events = [4, 5, 6]
)

my_mock_client.push_log()

Error: 在此处输入图像描述

This is my opinion, but I believe you are using decorator when it shouldn't be used. This is not intuitive and it implicitly changes the logic and signature of the decorated methods (ex instead of returning a single response it makes the method return a list of response s). I'd suggest making a simple "private" method instead that you'd call from inside of push_log and push_event :

class MyMockClient:
    def __init__(self, logs, events):
        self.logs = logs
        self.events = events

    def _iterate_pushes(self, obj):
        responses = []
        print(f"len: {len(obj)}")
        while len(obj) > 0:  # iterate until list is entirely popped
            response = obj.pop(0)  # when executed, it pops the first item
            responses.append(response)
            print(f"len: {len(obj)}")
        return responses

    def push_log(self):
        # Send first log and pops it if successful
        log = self.logs[0]
        print(log, "some extra logics for logs")
        
        return self._iterate_pushes(self.logs)

    def push_event(self):
        # Send first event and pops it if successful
        event = self.events[0]
        print(event, "some extra logics for events")
        
        return self._iterate_pushes(self.events)

Answer to your question (don't recommend):

Use getattr and pass a field name:

class MyMockClient:
    ...
    def __iterate_pushes(self, obj_name):
        """
        Decorate function to iterate the 'pushes' until
        the lists are entirely popped.
        """
        def outer_wrapper(self, decorated_func):
            def inner_wrapper(self):
                responses = []
                obj = getattr(self, obj_name)
                ...
                return responses

            return inner_wrapper

        return outer_wrapper

    @__iterate_pushes(obj_name='logs')
    def push_log(self):
        ...
        return response

    @__iterate_pushes(obj_name='events')
    def push_event(self):
        ...
        return response

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