I. Closure
1. Definition of closure
Closures are a special mechanism in programming languages, belonging to higher-order functions. Specifically, they
occur when:
1. functions are nested (a function is defined inside another function);
2. the inner function uses variables (or parameters) of the outer function; and
3. the outer function returns a value from the inner function.
2. Sample Code
# The external function returns the internal function, which uses the variables/arguments of the external function
def outer_func(num1):
# Num1 is the input parameter
outer_num=num1
def inner_func(num2):
# Declare variables as external function variables using the nonlocal keyword
nonlocal outer_num
# Modify external function variables
outer_num+=1
print(f'inner func return {outer_num+num2}')
return outer_num+num2
print(outer_num)
inner_func(3)
print(outer_num)
return inner_func
if __name__ == '__main__':
f=outer_func(2) # Returns an internal function that prints 2 first, then outer_num becomes 3, prints inner funcs, and then prints 3
print(f) # Original outer_num=3, then outer_num+=1 becomes 4, print inner funct return 8, and then print 8
3. Advantages of closures
1. Enables the decorator pattern.
2. Enables the function factory pattern.
3. Enables function modularization and reuse.
4. Disadvantages of closures
1. Increases code complexity;
2. Makes debugging difficult;
3. Makes memory leaks more likely.
II. Decorative Objects
1. Definition of Decorator
A decorator is a function that adds extra functionality to an existing function; it is essentially a closure function.
Features of decorators:
- Without modifying the source code of existing functions
- Without modifying the calling convention of existing functions
- Add extra functionality to an existing function
Decorator usage:
Python provides a simpler way to decorate functions: @decorator_name; which can decorate existing functions.
# Do not modify the code and calling methods of existing functions
# Add additional functionality to existing functions
def login(func): # The parameter must be a function
print('decorator starts executing')
def inner_func():
print('check and login')
func()
return inner_func
# Login is required before posting comments
def comment():
print('commit a comment')
@login # uses @ syntax to indicate that it uses login to decorate the comment function, similar to annotations in Java
def comment2():
print('commit a Comment 2')
if __name__ == '__main__':
# Method 1: Directly use, the disadvantage is writing an extra line of nested code
# Add a login decorator to the comment() method and return inner_func
comment = login(comment)
# Execute inner_func, first print: Check if not logged in and log in, then print: Commit a comment
comment()
# Method 2: Use the decorator with @, keeping all other codes unchanged
comment2()
Note: @check is equivalent to comment = check(comment)
2. Use Cases
Add a function to output the execution time of multiple functions.
# Add the function of printing execution time to multiple functions
import time
def timer(func):
def inner_func():
start = time.time()
func()
end = time.time()
print(f'function {func. __name__} running {end-start} seconds')
return inner_func
@timer
def test1():
print('test1')
time.sleep(1)
def test2():
print('test2')
time.sleep(2)
@timer
def test3():
print('test3')
time.sleep(2)
if __name__ == '__main__':
test1()
test2()
test3()
3. Decorators with parameters
Decorators with parameters allow you to pass specified arguments when decorating a function. The syntax is: @decorator(parameter, …
- Decorators can only accept one parameter, and that parameter must be a function.
- Wrap the decorator with another function, let the outermost function receive the parameters, and return the decorator.
Case requirement: Write a decorator to add authentication functionality (user list from a file) to multiple functions. The requirement is that after a successful login, subsequent functions can be executed directly without requiring a username and password.
# Decorator with parameters
# On the basis of the decorator, wrap another layer of function and return the decorator (3rd order function)
from functools import wraps
# The user data file contains the following content:
# {'name':'boy','passwd':'123456'},
# {'name':'girl','passwd':'123456'},
file_path='../resource/users.data'
# check logged in
already_login = False
def login(file_path):
# Initialize user information and read users from the file (the file will be loaded multiple times, the number of times=@login())
user_list = []
with open(file_path,'r',encoding='utf-8') as f:
for line in f.readlines():
user_list.append(eval(line))
print ('successfully loaded user list') # This line will be printed three times. If you want to load only once, you can consider turning user_ist and the loading process into global variables
def login_decorator(func):
@wraps(func) # Use the built-in wrap decorator to disguise funcs, so that the original func function does not lose its identity information after being decorated
def inner_func(*args, **kwargs):
global already_login
# Check if logged in and skip verification; Otherwise, log in
# Because it needs to take effect globally, this variable needs to be defined outside of login
if not already_login:
print(f'The user intends to execute {func. __name__}, but has not logged in, please log in first')
name=input('username:\t')
passwd=input('Password:\t')
for user in user_list:
if user['name'] == name and user['passwd'] == passwd:
print('User login successful, execute function')
func(*args, **kwargs)
# Modify global variables to identify logged in status
already_login = True
break
else:
print(f'account password incorrect, do not execute {func. __name__}')
else:
print('User logged in, execute directly')
func(*args, **kwargs)
return inner_func
return login_decorator
@login(file_path)
def test1():
print('test1')
@login(file_path)
def test2():
print('test2')
@login(file_path)
def test3():
print('test3')
if __name__ == '__main__':
print (the name of the f'test1 function is: {test1. __name__} ') # Verify if the wraps disguise is effective
test1()
test2()
test3()
4. How to write decorator classes
Another way to implement decorators is by defining a class to decorate the function. This requires using a `call` function within the class to turn the class instance into a callable object.
# Decorator class, rewrite the __call__ method to implement the content of the inner function inside
class Login:
def __init__(self,func):
self.func = func
# If it is a decorator class, the call function must be rewritten
def __call__(self,*args,**kwargs):
print('Check if not logged in, then log in first')
self.func(*args,**kwargs)
@Login
def test1():
print("test1")
if __name__ == '__main__':
print(test1.__class__.__name__)
test1()
5. Property decorator
The property decorator is a built-in Python decorator that allows you to treat a method as an attribute, simplifying the code when calling it.
class Person:
def __init__(self):
self.__age = 0
# Obtain age
@property
def age(self):
return self.__age
# This decorator uses age as an attribute to assign a value to age
@age.setter
def age(self,new_age):
if new_age>=200 or new_age<=0:
raise Exception('Illegal Age')
else:
self.__age = new_age
if __name__ == '__main__':
p = Person()
# Operate private attributes like regular attributes
p.age=10
print(p.age)
6. The implementation principle of property decorators
# property is actually a descriptor class
class MyProperty:
"""Simplified version of property implementation"""
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("not writable")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("cannot be deleted")
self.fdel(obj)
def setter(self, fset):
"""Create a new property object and set a new setter"""
return type(self)(self.fget, fset, self.fdel)
# Use custom properties
class Demo:
def __init__(self, x):
self._x = x
def get_x(self):
return self._x
def set_x(self, value):
self._x = value
x = MyProperty(get_x, set_x)
demo = Demo(10)
print(demo.x) # 10
demo.x = 20 # Call set_x
print(demo.x) # 20