I want to write a class Tool whose some member functions will be processed by another class, so I want to mark them explicitly and elegantly. For example,
class Tool:
def __init__(self):
self._marked = []
#mark # how to add func1 to self._marked?
def func1():
"""I will be processed"""
def func2():
"""I will not be processed"""
def marked_funcs():
"""return all marked functions"""
return self._marked
However, the function decorator mark cannot access self, so I cannot add the decorated functions to self._marked. How can I do this, or is there any other elegant way to achieve this requirement?
I think this is close to what you want to achieve.
I find it difficult to design a decorator to edit the class object (self._marked in the original code). So I create a "global" storage for the marked functions (_marked).
When invoking the mark decorator, this function is added to the _marked set.
I define the marked_funcs as a class method. This is because what the decorator receive is Tool.func1, not the method of the instance.
As a result, _marked_funcs returns its member functions that are marked.
_marked = set()
def mark(func):
_marked.add(func)
return func
def is_marked(func):
return callable(func) and (func in _marked)
# test
#mark
def one():
return 1
is_marked(one)
# True
class Tool:
#mark # how to add func1 to self._marked?
def func1():
"""I will be processed"""
def func2():
"""I will not be processed"""
#classmethod
def marked_funcs(cls):
"""return all marked functions"""
out = []
for name in dir(cls):
obj = getattr(cls, name)
if is_marked(obj):
out.append(obj)
return out
t = Tool()
t.marked_funcs()
# [<function __main__.Tool.func1()>]
Tool.marked_funcs()
# [<function __main__.Tool.func1()>]
I have a simple idea and it works. There is no need to use a list to store marked functions. I can add new property to the functions that I need to mark, and when necessary, I search for functions which has this property through traversal. For example:
def register(func):
func.x = 1
return func
class A:
#register
def hello1(self, a, b, c):
print(f"a={a}, b={b}, c={c}")
#register
def hello2(self, a, b, c):
print(f"a={a}, b={b}, c={c}")
def registered_functions(self):
res = []
for func_name in dir(self):
func = getattr(self, func_name)
if hasattr(func, 'x'):
res.append(func)
return res
if __name__ == '__main__':
a = A()
print(a.registered_functions())
# [<bound method A.hello1 of <__main__.A object at 0x7f7bd815aca0>>, <bound method A.hello2 of <__main__.A object at 0x7f7bd815aca0>>]
Related
In python, how can I setup a parent class to track methods with a specific decorator for each child seperatly? A quick code snippet of what I am trying to do:
class Parent:
decorated_func_dict = {} #dictionary that stores name->func for decorated functions
def get_func_by_decorator_name(self, name):
#stuff
pass
class Child1(Parent):
#func_name("Bob")
def bob_func(self, *args):
pass
#func_name("Tom")
def func2(self, *args):
pass
class Child2(Parent):
#func_name("Bob")
def func_bob2(self, *args):
pass
foo = Child1()
bar = Child2()
foo.get_func_by_decorator_name("Bob")
#Returns foo.bob_func
bar.get_func_by_decorator_name("Bob")
#Returns bar.func_bob2
Using Python 3.9.
A decorator is not something that makes a function look pretty. It is a callable that ingests an object (not only functions), does some arbitrary operations, and returns a replacement object.
In this case, your decorator should be storing references to function objects in a dictionary somewhere. The problem is that you won't be able to reference the class in which the functions are defined until it is created, which happens well after the decorator is run. You can avoid this by storing the name of the class as well as the name of the function.
The final step here is to properly bind the function objects to methods on the right object. That is something that get_func_by_decorated_name can do for you.
In sum, you can write something like this:
decorated_func_dict = {}
def func_name(cls_name, func_name):
def decorator(func):
decorated_func_dict.setdefault(cls_name, {})[func_name] = func
return func
return decorator
class Parent:
def get_func_by_decorator_name(self, name):
return decorated_func_dict[type(self).__name__][name].__get__(self)
class Child1(Parent):
#func_name("Child1", "Bob")
def bob_func(self, *args):
pass
#func_name("Child1", "Tom")
def func2(self, *args):
pass
class Child2(Parent):
#func_name("Child2", "Bob")
def func_bob2(self, *args):
pass
And indeed you get:
>>> foo.get_func_by_decorator_name("Bob")
<bound method Child1.bob_func of <__main__.Child1 object at 0x000001D58181E070>>
>>> bar.get_func_by_decorator_name("Bob")
<bound method Child2.func_bob2 of <__main__.Child2 object at 0x000001D582041F10>>
Another way to do this is to give your functions a name attribute, which you can then aggregate into a mapping in __init_subclass__ in Parent. This allows you to make an interface a bit closer to what you originally intended:
def func_name(func_name):
def decorator(func):
func.special_name = func_name
return func
return decorator
class Parent:
def __init_subclass__(cls):
cls.decorated_func_dict = {}
for item in cls.__dict__.values():
if hasattr(item, 'special_name'):
cls.decorated_func_dict[item.special_name] = item
del item.special_name # optional
def get_func_by_decorator_name(self, name):
return self.decorated_func_dict[name].__get__(self)
class Child1(Parent):
#func_name("Bob")
def bob_func(self, *args):
pass
#func_name("Tom")
def func2(self, *args):
pass
class Child2(Parent):
#func_name("Bob")
def func_bob2(self, *args):
pass
The results are identical to the first example.
The easiest way would of course be to get access to the child's namespace before the class is created, e.g. with a metaclass.
So I have a class with a method, which takes string. Somethinkg like this:
class A():
def func(self, name):
# do some stuff with it
I have finite number of possible values, [val1, val2, val2] for example, All strings. I want to use them like this:
a = A()
a.val1() # actually a.func(val1)
I tried to combine decorators and setattr:
class A():
def func(self, val):
# do some stuff with it
def register(self, val):
def wrapper(self):
self.func(val)
setattr(self, val, wrapper)
So I can iterate through all possible values in run-time:
a = A()
for val in vals:
a.register(val)
And it has zero effect. Usually setattr adds new attribute with value None, but in this case nothing happens. Can somebody explain why it is this way and what can I do?
register() isn't a decorator, it's mostly just a "function factory" with side-effects. Also, as I said in a comment, setattr() needs to know what name to assigned to the value.
Here's a way to get your code to work:
class A():
def func(self, val):
# do some stuff with it
print('func({}) called'.format(val))
def register(self, val, name):
def wrapper():
self.func(val)
wrapper.__name__ = name
setattr(self, name, wrapper)
vals = 10, 20, 30
a = A()
for i, val in enumerate(vals, 1):
a.register(val, 'val'+str(i)) # Creates name argument.
a.val1() # -> func(10) called
a.val2() # -> func(20) called
Given a class and a set of its methods - how can you determine from within that class which methods have been decorated with a certain decorator?
My goal is to basically get the actual values of the methods that are decorated, so something like:
class A():
def get_values(self):
...
# returned {'a-special-name': 1, 'b': 2}
#my_dec('a-special-name') # Ideally be able to also put optional name
def a(self):
return 1
#my_dec
def b(self):
return 2
Any idea on how to accomplish this?
Edit: this should also work on parent classes, that is, if A is a subclass of:
class B():
#my_dec
def c(self):
return 3
then get_values() of A instance should return {'a-special-name': 1, 'b': 2, 'c': 3} (order is irrelevant of course)
Edit: class based decorator that works but not with inheritance. Any idea how to make it work with inheritance but without having to decorate the class itself?
class my_dec(object):
def __init__(self, func, name=None):
self.func = func
self.name = name or func.__name__
self.func._some_flag = True
def __get__(self, instance, cls=None):
if instance is None:
return self
return self.func(instance)
If you can define the decorator yourself, then simply have it "mark" the method object in some way:
def my_dec(method):
method._this_be_decorated = True
return method
The class can then look for those marked methods; something like:
from inspect import isfunction
class A:
def get_values(self):
return filter(lambda i: isfunction(i) and hasattr(i, '_this_be_decorated'),
vars(type(self)).values())
This will return an iterable of function objects which you can process further as needed.
def my_dec(name):
if callable(name):
# name is callable – take its name
func = name # no make the code more readable
func.special_name = func.__name__
return func
else:
# name is the name to give – add an inner layer of functions
def inner(function_object):
function_object.special_name = name
return function_object
return inner
class A():
def get_values(self):
# return a dict of special name to call result mapping for every class member that has a special_name.
return {func.special_name: func(self) for func in self.__class__.__dict__.values() if hasattr(func, 'special_name')}
# returned {'a-special-name': 1, 'b': 2}
#my_dec('a-special-name') # Ideally be able to also put optional name
def a(self):
return 1
#my_dec
def b(self):
return 2
def no_dec(self):
return 42
should do what you want.
As deceze mentions, decorators can do whatever they want so there's no reliable generic answer. If you "own" the decorator you can add special properties to it's return value ie (Q&D py2.7 example):
def mydec(name=''):
# py27 hack - for py3 you want nonlocal instead
n = [name]
def innerdec(func):
# py27 hack - for py3 you want nonlocal instead
name = n[0] or func.__name__
def wrapper(*args, **kw):
print("in mydec.wrapper for {}".format(name))
return func(*args, **kw)
wrapper.ismydec = True # so we know this is decorated by mydec
wrapper.func = func # so we can get the original func
wrapper.name = name
return wrapper
return innerdec
def collect_dec(cls):
decorated = {}
for attname in dir(cls):
obj = getattr(cls, attname)
if getattr(obj, "ismydec", False):
decorated[obj.name] = obj.func
cls._decorated_funcs = decorated
return cls
#collect_dec
class A():
def get_values(self):
return {
name:func(self) for name, func in self._decorated_funcs.items()
}
#mydec('a-special-name') # Ideally be able to also put optional name
def a(self):
return 1
#mydec() # no name
def b(self):
return 2
a = A()
print(a.get_values())
Which outputs:
{'a-special-name': 1, 'b': 2}
This question already has answers here:
How can I access variables from the caller, even if it isn't an enclosing scope (i.e., implement dynamic scoping)?
(4 answers)
Closed 6 months ago.
I want to do this (dummy example):
def func():
nonlocal var
print (var)
class A:
var = 'hola'
func()
But I get: "SyntaxError: no binding for nonlocal 'var' found"
What I really intend to do is append a method name to a list in the scope of the class if that method is decorated. Something like this:
def decorator(func):
nonlocal decorated
decorated.append(func.__name__)
return func
class A:
decorated = []
#decorate
def f(self):
pass
Python just doesn't let you do this. You can access the class namespace by using locals(). But at this point, you might as well just pass the variable you're interested in to the decorator.
# using locals()
def decorator(class_namespace):
def _decorator(func):
class_namespace["decorated"].append(func)
return func
return _decorator
class A:
store = decorator(locals())
decorated = []
#store
def func(self):
pass
del store
Generally, it's easy to use a pair of decorators. One to mark the functions you're interested in, and one to collect them.
from types import FunctionType
def collect(cls):
for item in vars(cls).values():
print(item)
if isinstance(item, FunctionType) and getattr(item, "marked", False):
cls.marked_funcs.append(item)
return cls
def mark(func):
func.marked = True
return func
#collect
class B:
marked_funcs = []
#mark
def func(self):
pass
But in your case it might just be simpler to create the set of function names at the end of the class. eg.
class C:
def func(self):
pass
func_names = [f.__name__ for f in [func]]
Use the decorator to mark the functions, then have decorated be a class method which returns all decorated functions.
import inspect
def decorate(func):
func.decorated = True
return func
class A:
def foo():
print('foo')
#decorate
def bar():
print('bar')
#classmethod
def decorated(cls):
def is_decorated_method(obj):
try:
return inspect.isfunction(obj) and obj.decorated
except AttributeError:
# The object has no decorated attribute
return False
return [result[1] for result in inspect.getmembers(cls, predicate=is_decorated_method)]
print(A.decorated())
# [<function A.bar at 0x6ffffc0aa60>]
y = 20
def f():
x = 7
def g():
nonlocal x # 7
global y # 20
nonlocal qualifier refers to a name in the outer function scope, which does not include the module scope. While global does the complementary. So you are using nonlocal incorrectly.
How about that?
decorated = []
def decorator(func):
decorated.append(func.__name__)
def wrapper(self):
print('wrapper called')
func(self)
return wrapper
class A:
#decorator
def f(self): print('function called')
print(decorated)
A().f()
OUTPUT:
['f']
wrapper called
function called
NOTES:
The code you provided experiencing the issue you've described because var is class variable so it must be referenced as A.var but you cannot do that in your code because you try to use it before A is defined. if it were different classes it would be possible:
class X:
decorated = []
def decorator(func):
X.decorated.append(func.__name__)
return func
class A:
#decorator
def f(self):
pass
print(X.decorated)
Please note that you don't need to specify nonlocal if you don't assign to variable but call methods like append().
I know there is similar question, but my scenario is somehow different: refer to codes:
class MyClass(object):
def __init__(self, log_location)
self.logs = logging(log_location) # create log object by the log_location, this object should be used by the decorator fucntion
def record_log(log_object):
""" this is the decorator function
"""
def deco(func):
def wrap(*args, **kwargs):
rs = func()
# use log object to record log
if rs:
log_object.record('success')
else:
log_object.record('fail')
return wrap
return deco
#record_log(self.logs)
def test(self):
rs = do_some_thing
if rs:
return True
return False
def main():
my_class = MyClass()
my_class.test()
But, there is an error like this:
#record_log(self.logs)
NameError: name 'self' is not defined
Hos should I use the instance attribute self.logs in a decorator function in such scenario like this??
Thanks very much!
You can not pass a reference to self or any attribute of self at this point. The #record_log line is executed (the method is decorated) before the code in main is executed, i.e. before any instance of MyClass is created at all -- in fact, even before the definition of MyClass has been completed! But remember that
#record_log(self.logs)
def test(self, n):
is actually just syntactic sugar for
test = record_log(self.logs)(test)
So one way to work around your problem would be to redefine test in your __init__, i.e.
def __init__(self, log_location)
self.logs = logging(log_location)
self.test = record_log(self.logs)(self.test)
Also note that your decorator is not passing any parameters to func and not returning the results. Also, it should probably be defined on module level (before the class).
def record_log(log_object):
def deco(func):
def wrap(*args, **kwargs):
rs = func(*args, **kwargs) # pass parameters
if rs:
log_object.record('success')
else:
log_object.record('fail')
return rs # return result
return wrap
return deco
There are several objections about your code:
deco() is redundant. You can directly return wrap from record_log().
If you only plan to decorate MyClass's methods, then there is no point in passing log_object to the decorator, as self.logs will always be used. Otherwise, consider moving the decorator to module level, as already suggested by others.
The decorated method's return value is currently lost.
The call to the decorated function does not pass self to it.
The proper code would therefore be:
class MyClass(object):
def __init__(self, log_location):
self.logs = logging(log_location)
def record_log(func):
""" this is the decorator function
"""
def wrap(self):
rs = func(self)
# use log object to record log
if rs:
print 1
self.logs.record('success')
else:
print 2
self.logs.record('fail')
return rs
return wrap
#record_log
def test(self):
rs = do_some_thing
if rs:
return True
return False