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