0

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
nadapez
  • 2,603
  • 2
  • 20
  • 26
  • 1
    `nonlocal` looks in the scope of the function definition, not the function call. There are ways to look in the scope of the function call, but it's generally recommended to avoid them if you possibly can. Try giving the list to the decorator as an argument, or using a class decorator to post-process the class and set up the `decorated` list. – user2357112 Apr 11 '16 at 17:05

4 Answers4

2

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>]
Marc J
  • 1,303
  • 11
  • 11
2

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]]
Dunes
  • 37,291
  • 7
  • 81
  • 97
1
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.

C Panda
  • 3,297
  • 2
  • 11
  • 11
0

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().

Grief
  • 1,839
  • 1
  • 21
  • 40