Add files via upload · fluentpython/example-code-2e@4deb247

1+

def make_upper(func):

2+

def inner_func(arg): # def inner_func(*args, **kwargs):

3+

return func(arg).upper() # return func(*args, **kwargs).upper() -- to support compatibility for all the type of functions irrespective of function signature

4+

return inner_func

5+6+7+

@make_upper

8+

def remove_vowel(in_data):

9+

vowels = list('aeiou')

10+

print(vowels)

11+

consonants = [char for char in in_data.lower() if char not in vowels]

12+

return ''.join(consonants)

13+14+

print(remove_vowel('Ezhilarasi'))

15+16+

# <h2>Data in Decorators</h2>

17+

# Decorater function with shared variables

18+

def running_average(func):

19+

data = {"total" : 0, "count" : 0} #Run once for every decoraters and data got created. Whenever decorated function call happens just the wrapper function getting called not the entire decorator

20+

def wrapper(*args, **kwargs):

21+

val = func(*args, **kwargs)

22+

data["total"] += val

23+

data["count"] += 1

24+

print("Average of {} so far: {:.01f}".format(

25+

func.__name__, data["total"] / data["count"]))

26+

return func(*args, **kwargs)

27+

return wrapper

28+29+

"""

30+

The decorator function itself is executed exactly once for every function it decorates. If you decorate N functions, running_average() is executed N times, so we get N different data dictionaries, each tied to one of the resulting decorated functions. This has nothing to do with how many times a decorated function is executed. The decorated function is, basically, one of the created wrapper() functions. That wrapper() can be executed many times, using the same data dictionary that was in scope when that wrapper() was defined.

31+

"""

32+33+

#<h2>Accessing Inner Data</h2>

34+

def collectstats(func):

35+

data = {"total" : 0, "count" : 0}

36+

def wrapper(*args, **kwargs):

37+

val = func(*args, **kwargs)

38+

data["total"] += val

39+

data["count"] += 1

40+

return val

41+

wrapper.data = data #ties the data's current value with the wrapper function

42+

return wrapper

43+44+

#decorator to count how many times a function being called

45+

def countcalls(func):

46+

count = 0

47+

def wrapper(*args, **kwargs):

48+

nonlocal count #Notice nonlocal keyword

49+

count += 1

50+

print(f"# of calls: {count}")

51+

return func(*args, **kwargs)

52+

return wrapper

53+54+55+

# Decorators That Take Arguments

56+

def add(increment): #one more nested function when we add arguments to decorators

57+

def decorator(func):

58+

def wrapper(*args, **kwargs):

59+

return func(*args, **kwargs) + increment # increment will vary based on the argument passed

60+

return wrapper

61+

return decorator

62+63+

@add(2) # add is the function that takes argument and return decorator function

64+

def foo(x):

65+

return x ** 2

66+67+

@add(4)

68+

def bar(n):

69+

return n * 2

70+71+72+

# Class-Based Decorators

73+

class PrintLog:

74+

def __init__(self, func): # pass the function as arguments to constructor

75+

self.func = func

76+

def __call__(self, *args, **kwargs): # replace wrapper with __call__

77+

print(f"CALLING: {self.func.__name__}")

78+

return self.func(*args, **kwargs)

79+80+

@PrintLog

81+

def foo(x):

82+

print(x + 2)

83+84+85+

# class based decorators with nonlocal variables

86+

class CountCalls:

87+

def __init__(self, func):

88+

self.func = func

89+

self.count = 0 #count can be accessed outside decorator function

90+

def __call__(self, *args, **kwargs):

91+

self.count += 1

92+

print(f"# of calls: {self.count}")

93+

return self.func(*args, **kwargs)

94+95+

@CountCalls

96+

def foo(x):

97+

return x + 2

98+99+

# Class-based version of the "add" decorator above.

100+

class Add:

101+

def __init__(self, increment): # parameter accepted here

102+

self.increment = increment

103+

def __call__(self, func): # accepts function to be decorated

104+

def wrapper(n): # nested function

105+

return func(n) + self.increment

106+

return wrapper

107+108+109+

# Decorators for Classes

110+111+

def autorepr(cls): #accepts class

112+

def cls_repr(self):

113+

return f"{cls.__name__}()"

114+

cls.__repr__ = cls_repr # redefines the function implementation

115+

return cls # returns original class not a wrapper function

116+117+

@autorepr

118+

class Penny:

119+

value = 1

120+121+

penny = Penny()

122+

repr(penny)

123+

'Penny()'