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