Is it possible to access decorator attributes in Python 3?
For example: is it possible to access self.misses after the call to the decorated fibonacci method?
class Cache:
def __init__(self, func):
self.func = func
self.cache = {}
self.misses = 0
def __call__(self, *args):
if not (args in self.cache):
self.misses += 1
self.cache[args] = self.func(*args)
return self.cache[args]
#Cache
def fibonacci(n):
return n if n in (0, 1) else fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(20)
### now we want to print the number of cache misses ###
When you decorate a function (or class, or anything else), you're actually replacing the decorated object with the return value of the decorator. That means that this:
#Cache
def fibonacci(n):
return n if n in (0, 1) else fibonacci(n - 1) + fibonacci(n - 2)
is equivalent to this:
def fibonacci(n):
return n if n in (0, 1) else fibonacci(n - 1) + fibonacci(n - 2)
fibonacci = Cache(fibonacci)
Consequently, fibonacci is now a Cache instance and not a function:
>>> fibonacci
<__main__.Cache object at 0x7fd4d8b63e80>
So in order to get the number of cache misses, you just need to access fibonacci's misses attribute:
>>> fibonacci.misses
21
Related
I want to write a decorator that will compute runtime of function that compute fibonachi number of 5(fib(5)) 10 times and will print a medium runtime value. But my decorator returns an error. How to fix this problem?
import functools
import time
def trace(times):
def cache(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
total = 0
for i in range(times):
start = time.time()
func(*args, **kwargs)
end = time.time()
total += (end - start)
print('Medium value is {}'.format(total / times))
return func
return wrapper
return cache
#trace(times=10)
def fib(num):
if num < 2:
return num
return fib(num - 1) + fib(num - 2)
fib(5)
First, instead of returning func in the wrapper return the result of it.
We have to make sure the tracing only happens for the first call to fib and none of the recursive calls. Let's use a boolean traced argument:
import functools
import time
def trace(times):
def cache(func):
#functools.wraps(func)
def wrapper(*args, traced=True, **kwargs):
if not traced:
return func(*args, **kwargs)
total = 0
for i in range(times):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
total += (end - start)
print(f'run {i}, time is {total}')
print(f'Mean value is {total / times}')
return result
return wrapper
return cache
#trace(times=10)
def fib(num, traced=True):
if num < 2:
return num
return fib(num - 1, traced=False) + fib(num - 2, traced=False)
fib(5)
traced is True by default so any call to fib(n) will be traced (no need for user to be aware of the traced argument). However the recursive calls all have traced=False therefore they don't trigger the loop in the decorator.
I tried :
def fibonnaci(n):
total_call = 0
if n ==0 or n == 1:
return 1
else:
if n== 2 or n == 1:
total_call +=0
else:
total_call +=2
return fibonnaci(n - 1) + fibonnaci(n - 2), total_call
n = 8
print(fibonnaci(n))
but I got a error:
TypeError: can only concatenate tuple (not "int") to tuple
How to display the number of calls for fibonnaci?
In your return statement the result of both fibonnaci(n - 1) and fibonnaci(n - 2) could be a tuple (argument > 1), or a single integer (argument <= 1) thus + means concatenation when the first one is a tuple. But when n == 3 in return fibonacci(n - 1) + fibonacci(n - 2), total_call fibonacci(2) is a tuple ((2, total_call)), while fibonacci(1) is an integer (1). So you want to concatenate a tuple with with an integer, which is impossible.
Using Decorators
Using Function Attributes
Reference
Code
def call_counter(func):
" Does the call count for any function "
def helper(x):
helper.calls += 1
return func(x)
helper.calls = 0
return helper
#call_counter
def fib(n):
if n ==0 or n == 1:
return 1
return fib(n - 1) + fib(n - 2)
Usage
fib(5)
print(fib.calls)
fib(10)
print(fib.calls) # Keeps running total so will be from previous
# fib(5) plus current fib(10)
# To reset counter
fib.calls = 0
Using Class
Reference
Code
class countCalls(object):
"""Decorator that keeps track of the number of times a function is called.
::
>>> #countCalls
... def foo():
... return "spam"
...
>>> for _ in range(10)
... foo()
...
>>> foo.count()
10
>>> countCalls.counts()
{'foo': 10}
Found in the Pythod Decorator Library from http://wiki.python.org/moin web site.
"""
instances = {}
def __init__(self, func):
self.func = func
self.numcalls = 0
countCalls.instances[func] = self
def __call__(self, *args, **kwargs):
self.numcalls += 1
return self.func(*args, **kwargs)
def count(self):
"Return the number of times this function was called."
return countCalls.instances[self.func].numcalls
#staticmethod
def counts():
"Return a dict of {function: # of calls} for all registered functions."
return dict([(func.__name__, countCalls.instances[func].numcalls) for func in countCalls.instances])
#countCalls
def fib(n):
if n ==0 or n == 1:
return 1
return fib(n - 1) + fib(n - 2)
Example
print(fib(3)) # Output 3
print(fib.count()) # Output 5
Advantage
Allows obtaining counts of all registered functions (i.e. registered by using decorator)
#countCalls
def f(n):
pass # dummy function
#countCalls
def g(n):
pass # dummy function
for i in range(5):
f(i)
for i in range(10):
g(i)
print(countCalls.counts())
# Outputs: {'f': 5, 'g': 10}
def fib(n):
if n <= 1:
return n, 1
fib_one = fib(n - 1)
fib_two = fib(n - 2)
#Return the result and the number of function calls (+ 1 for the current call)
return fib_one[0] + fib_two[0], fib_one[1] + fib_two[1] + 1
if __name__ == '__main__':
number_of_function_calls = fib(4)[1]
Fib(4) should return 9, which it does
fib(4)
fib(3) fib(2)
fib(2) fib(1) fib(1) fib(0)
fib(1) fib(0)
The problem is "obvious", if you bother to trace the values you're using:
return fibonnaci(n - 1) + fibonnaci(n - 2), total_call
When n is 3, this tries to "add" fibonnaci(2), a tuple, and fibonnaci(1), the integer 1. This is not a legal operation. You need to regularize your return values. You can't magically return the value alone (not the count) when that's what you want; you have to explicitly program the difference: dismember the tuple and add the component values.
Start with your base case being
return 1, 0
Your recursion case needs to add the components. Implementation is left s an exercise for the student.
Here's another answer using a decorator. The advantage of using a decorator is that the base function fib does not need to change. That means the total_count code and multiple return values can be discarded from your original attempt -
#counter(model = fib_result)
def fib(n = 0):
if n < 2:
return n
else:
return fib(n - 1) + fib(n - 2)
Our decorator counter accepts a model so we can respond to the behavior of the decorated function. Our decorated fib will return a fib_result such as { result: ?, count: ? }. That means we also need to handle fib(?) + fib(?) which is why we also defined __add__ -
class fib_result:
def __init__(self, result, count = 0):
if isinstance(result, fib_result):
self.result = result.result
self.count = result.count + count
else:
self.result = result
self.count = count
def __add__(a, b):
return fib_result(a.result + b.result, a.count + b.count)
As you can see, this fib_result is specific to counting fib calls. A different model may be required for counting other recursive functions that return other types of results.
All that's left now is to define our generic counter decorator. Our decorator takes arguments and returns a new decorator, lambda f: ..., which simply captures the passed arguments, lambda *args: ..., and constructs a new model with the result of f(*args) and a base count of 1 -
def counter(model = tuple):
return lambda f: lambda *args: model(f(*args), 1)
Here's how the complete program works -
r = fib(4)
print(f"answer: {r.result}, recurs: {r.count}")
# answer: 3, recurs: 9
r = fib(10)
print(f"answer: {r.result}, recurs: {r.count}")
# answer: 55, recurs: 177
This question already has answers here:
How can I call a function within a class?
(2 answers)
Closed 3 years ago.
I am working on this problem https://leetcode.com/problems/climbing-stairs and getting an error 'global name helper is not defined'. But it is defined in the class?
class Solution(object):
def climbStairs(self, n, ):
"""
:type n: int
:rtype: int
"""
return helper(0, n)
def helper(self, curr, n):
if (curr > n):
return 0
if (curr == n):
return 1
return helper(curr + 1, n) + helper(curr + 2, n)
You are missing the 'self' argument for helper i.e replacing helper() with self.helper() should hopefully work.
class Solution(object):
def climbStairs(self, n, ):
"""
:type n: int
:rtype: int
"""
return self.helper(0, n)
def helper(self, curr, n):
if (curr > n):
return 0
if (curr == n):
return 1
return self.helper(curr + 1, n) + self.helper(curr + 2, n)
helper is a bound function of a Solution instance, you need to call it on self.
as per the timeout error on bigger inputs, that's because you make 2 recursive calls per call to helper, which means a runtime complexity of O(2^N).
because many of the calls are with the same arguments, you can use functools.lrucache to cache results of previous calls, which reduces it to O(N)
from functools import lru_cache
class Solution(object):
def climbStairs(self, n, ):
"""
:type n: int
:rtype: int
"""
return self.helper(0, n)
#lru_cache()
def helper(self, curr, n):
if (curr > n):
return 0
if (curr == n):
return 1
return self.helper(curr + 1, n) + self.helper(curr + 2, n)
s = Solution()
print(s.climbStairs(35))
Searching to build up my personal style of programming i want to be able to call a python class the same way i'd call a python function.
Here's what i mean:
consider this function:
def Factorial(n):
if n == 0:
return 1
else:
return n * Factorial(n - 1)
This is a function that outputs 24 when you call Factorial(4).
Now let's consider a class instead:
class Factorial:
def __call__(n):
if n == 0:
return 1
else:
return n * Factorial()(n - 1)
This code works the same way as the previous code except at call time where you instead write:
Factorial()(4) # which outputs 24
Now my question is how could you do this instead:
Factorial(4) # just that, and output 24, from THE object.
Thanks!
Use self.
class Factorial:
def __call__(self, n):
if n == 0:
return 1
else:
return n * self(n - 1)
f = Factorial()
f(3)
# 6
f(5)
# 120
From there you can easily add a starting value in your __new__ constructor.
class Factorial:
def __new__(cls, value=None):
instance = super().__new__(cls)
if value is None:
return instance
else:
return instance(value)
def __call__(self, n):
if n == 0:
return 1
else:
return n * self(n - 1)
Now you can
Factorial(5)
# 120
f = Factorial()
f(5)
# 120
Use a constructor (which is __init__ in Python) like so:
class Factorial:
def __init__(self, n):
// code here
You can override __new__ method:
class Factorial:
def __new__(cls, n):
if n == 0:
return 1
else:
return n * Factorial(n - 1)
Although the above solution works, I prefer a decorator-based solution which is less magical and is more intuitive and explicit:
def function_factory(cls):
return cls()
#function_factory
class Factorial:
def __call__(self, n):
if n == 0:
return 1
else:
return n * Factorial(n - 1)
A short & simple change in your existing code:
>>> class Factorial:
... def __call__(self,n):
... if n == 0:
... return 1
... else:
... return n * self.__call__(n - 1)
...
>>> Factorial = Factorial()
>>> Factorial(4)
24
>>>
I'm trying to implement a decorator that memoizes an arbitrary function. It appears I've successfully accomplished that with the following code:
def memoize(func):
cache = {}
def wrapper(*args, **kwargs):
acc = ""
for arg in args:
acc += str(arg)
if acc in cache:
return cache[acc]
else:
cache[acc] = func(*args, **kwargs)
return cache[acc]
return wrapper
#memoize
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
Then fib(100) returns 573147844013817084101 fairly quickly. However, if I don't use the syntactic sugar:
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
memoized = memoize(fib)
print memoized(100)
The function hangs. Debugging it, it looks like the wrapper returned is unable to modify the cache. Can someone explain this behavior? As far as I know there shouldn't be a difference between using the sugar and not using the sugar.
Your recursive call is not memoized, because you used a new name, not the original fib function name. Each fib() iteration calls back to fib(), but that will call the original, undecorated function.
Assign the return value of the decorator call to fib instead:
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
fib = memoize(fib)
print fib(100)
Alternatively, if you must use memoize as the name of the decorator result, have fib() call memoized() for recursive calls:
def fib(n):
if n == 0 or n == 1:
return 1
else:
return memoized(n - 1) + memoizzed(n - 2)
memoized = memoized(fib)
print memoized(100)
Remember, the #decorator syntax assigns to the same name, not a new name. The following two are equivalent:
#memoize
def fib(n):
# ....
and
def fib(n):
# ....
fib = memoize(fib) # Same name!
except the name fib is never bound to the original function first.