Closure & Decorator

Closure & Decorator

LEGB rules

  • Local, Enclosing, Gloable, Built-in

Local function

  • Useful for specialized, one-off functions
  • Aid in code organization and readability
  • Similar to lambdas, but more general
  • May contain multiple expressions
  • May contain statements

Closure

  • Closure maintain references to objects from earlier scopes
  • LEGB does not apply when making new bindings
  • Usage of nonlocal

    • Example

      def make_timer():
          last_called = None
      
          def elapsed():
              nonlocal last_called
              now = time.time()
              if last_called is None:
                  last_called = now
                  return None
              result = now - last_called
              last_called = now
              return result
          return elapsed
      
      
      if __name__ == "__main__":
          mt = make_timer()
          print(mt ())
          print('-----------------------------')
          print(mt ())
          print('-----------------------------')
          print(mt ())
      
  • Use as function factory

    • Example

      
      def raise_to(exp):
          def raise_to_exp(x):
              return pow(x, exp)
          return raise_to_exp
      
      if __name__ == "__main__":
          square=raise_to(2)
          cube= raise_to(3)
          print(square(2))
          print(cube(2))
      
      ## test result:
      ## 4
      ## 8
      
      

Decorator

  • Replace, enhance, or modify existing functions
  • Does not change the original function definition
  • Calling code does not need to change
  • Decorator mechanism uses the modified function’s original name
  • Example

    • use function as decorator

      def escape_unicode(f):
          def wrap(*args, **kwargs):
              x = f(*args, **kwargs)
              return ascii(x)
          return wrap
      
      
      @escape_unicode
      def hello_greek():
          return 'γειά σου κόσμος'    
      
      if __name__ == "__main__":
          print(hello_greek())
      
      ## test result:
      ## '\u03b3\u03b5\u03b9\u03ac \u03c3\u03bf\u03c5 \u03ba\u03cc\u03c3\u03bc\u03bf\u03c2'
      
    • Example: multiple decorators including function and instance

      class Trace:
          def __init__(self):
              self.enabled = True
      
          def __call__(self, f):
              @functools.wraps(f)
              def wrap(*args, **kwargs):
                  if self.enabled:
                      print('Calling {}'.format(f.__name__))
                  return f(*args, **kwargs)
              return wrap
      
      def escape_unicode(f):
          @functools.wraps(f)
          def wrap(*args, **kwargs):
              x = f(*args, **kwargs)
              return ascii(x)
          return wrap
      
      @tracer
      @escape_unicode
      def hello_greek():
          return 'γειά σου κόσμος'    
      
      ## test result
      ## Calling hello_greek
      ## '\u03b3\u03b5\u03b9\u03ac \u03c3\u03bf\u03c5 \u03ba\u03cc\u03c3\u03bc\u03bf\u03c2'
      
      
    • Use as validator

      def check_non_negative(index):
          def validator(f):
              def wrap(*args):
                  if args[index] < 0:
                      raise ValueError(
                          'Arg {} must be non-negative'.format(index)
                      )
                  return f(*args)
              return wrap
          return validator
      
      @check_non_negative(1)
      def create_seq(value, size):
          return [value]*size  
      
      if __name__ == "__main__":
          create_seq('0', -3)
      
      ## test result
      ....
      'Arg {} must be non-negative'.format(index)
      ValueError: Arg 1 must be non-negative
      

    Properties & Class

    @staticmethod

    • No access needed to either class or instance objects.
    • Most likely an implementation detail of the class.
    • May be able to be moved to become a module-scope function

    @classmethod

    • Requires access to the class object to call other class methods or the constructor
    • Always use self for the first argument to instance methods.
    • Always use cls for the first argument to class methods.
    • Use case: use as named constructors
    class FileStream(object):
    
        @classmethod
        def from_file(cls, filepath, ignore_comments=False, *args, **kwargs):   
            with open(filepath, 'r') as fileobj:
                for obj in cls(fileobj, ignore_comments, *args, **kwargs):
                    yield obj
    
        @classmethod
        def from_socket(cls, socket, ignore_comments=False, *args, **kwargs):
            raise NotImplemented ## Placeholder until implemented
    
        def __init__(self, iterable, ignore_comments=False, *args, **kwargs):
            ...
    

@property

  • Encapsulation

    • Example

      class A
          @property
          def  prop(self):
              return self._prop
      
          @prop.setter
          def prop(self, value):
              self._prop = value