Closures in metaclasses


F

Falcolas

I'm running into an issue with closures in metaclasses - that is, if I
create a function with a closure in a metaclass, the closure appears
to be lost when I access the final class. I end up getting the text
'param' instead of the actual tags I am expecting:

ALL_TAGS = ['a', 'abbr', 'acronym', 'address', 'applet', 'b', 'bdo',
'big'] #snip

def _tag_meta(name, bases, dict_):
for tag in ALL_TAGS:
def generic_tag(*args, **kwargs):
return Tag._generate_tag(tag, *args, **kwargs)
#generic_tag = eval("lambda *args, **kwargs: Tag._generate_tag
('%s', *args, **kwargs)" % tag)
dict_[tag] = staticmethod(generic_tag)
return type(name, bases, dict_)

class Tag(object):
__metaclass__ = _tag_meta
@staticmethod
def _generate_tag(tag_name, *args, **kwargs):
# Does the expected, following is just for the example's sake
return tag_name

Here's the output from my actual class:
$ python
Python 2.6.4 (r264:75706, Dec 7 2009, 18:45:15)
[GCC 4.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Using the eval shown above works as expected, but I'd rather go with
the closures, if someone can help me figure out what's going on.
 
Ad

Advertisements

A

Arnaud Delobelle

Falcolas said:
I'm running into an issue with closures in metaclasses - that is, if I
create a function with a closure in a metaclass, the closure appears
to be lost when I access the final class. I end up getting the text
'param' instead of the actual tags I am expecting:

ALL_TAGS = ['a', 'abbr', 'acronym', 'address', 'applet', 'b', 'bdo',
'big'] #snip

def _tag_meta(name, bases, dict_):
for tag in ALL_TAGS:
def generic_tag(*args, **kwargs):
return Tag._generate_tag(tag, *args, **kwargs)
#generic_tag = eval("lambda *args, **kwargs: Tag._generate_tag
('%s', *args, **kwargs)" % tag)
dict_[tag] = staticmethod(generic_tag)
return type(name, bases, dict_)

This is almost a FAQ and has nothing to do with metaclasses. The
simplest solution usually involves adding a default argument 'tag=tag'
to the function you define (here, generic_tag), but you can't do this
here because you have a **kwargs argument. Instead, you can use a
closure and do this for example:

def factory(tag):
def generic_tag(*args, **kwargs):
return Tag._generate_tag(tag, *args, **kwargs)
return generic_tag

def _tag_meta(name, bases, dict_):
for tag in ALL_TAGS:
dict_[tag] = staticmethod(factory(tag))
return type(name, bases, dict_)

Then your Tag example class will work as you expect:

class Tag(object):
__metaclass__ = _tag_meta
@staticmethod
def _generate_tag(tag_name, *args, **kwargs):
# Does the expected, following is just for the example's sake
return tag_name

I am sure that there are plenty of discussions of this issue in the
archives of c.l.python. I just can't think of a good keyword to google
it ATM.

However, I am usure about why you are using a metaclass.

HTH
 
F

Falcolas

Falcolas said:
I'm running into an issue with closures in metaclasses - that is, if I
create a function with a closure in a metaclass, the closure appears
to be lost when I access the final class. I end up getting the text
'param' instead of the actual tags I am expecting:
ALL_TAGS =  ['a', 'abbr', 'acronym', 'address', 'applet', 'b', 'bdo',
'big'] #snip
def _tag_meta(name, bases, dict_):
    for tag in ALL_TAGS:
        def generic_tag(*args, **kwargs):
            return Tag._generate_tag(tag, *args, **kwargs)
        #generic_tag = eval("lambda *args, **kwargs: Tag._generate_tag
('%s', *args, **kwargs)" % tag)
        dict_[tag] = staticmethod(generic_tag)
    return type(name, bases, dict_)

This is almost a FAQ and has nothing to do with metaclasses.  The
simplest solution usually involves adding a default argument 'tag=tag'
to the function you define (here, generic_tag), but you can't do this
here because you have a **kwargs argument.  Instead, you can use a
closure and do this for example:

def factory(tag):
    def generic_tag(*args, **kwargs):
        return Tag._generate_tag(tag, *args, **kwargs)
    return generic_tag

def _tag_meta(name, bases, dict_):
    for tag in ALL_TAGS:
        dict_[tag] = staticmethod(factory(tag))
    return type(name, bases, dict_)


I see - I was thinking it would preserve the closure from the for
statement, but I can see now why that would be wrong.
However, I am usure about why you are using a metaclass.

HTH

It was the easiest way I found to add a lot of static methods to the
Tag class without writing each one out. __getattr__ was not working
for this application. This is for a very simple application, and I
didn't want to add a lot of complexity to it's use. I'm always open
for other options OTOH.
 
A

Arnaud Delobelle

Falcolas said:
I'm running into an issue with closures in metaclasses - that is, if I
create a function with a closure in a metaclass, the closure appears
to be lost when I access the final class. I end up getting the text
'param' instead of the actual tags I am expecting:
ALL_TAGS =  ['a', 'abbr', 'acronym', 'address', 'applet', 'b', 'bdo',
'big'] #snip
def _tag_meta(name, bases, dict_):
    for tag in ALL_TAGS:
        def generic_tag(*args, **kwargs):
            return Tag._generate_tag(tag, *args, **kwargs)
        #generic_tag = eval("lambda *args, **kwargs: Tag._generate_tag
('%s', *args, **kwargs)" % tag)
        dict_[tag] = staticmethod(generic_tag)
    return type(name, bases, dict_)
This is almost a FAQ and has nothing to do with metaclasses.  The
simplest solution usually involves adding a default argument 'tag=tag'
to the function you define (here, generic_tag), but you can't do this
here because you have a **kwargs argument.  Instead, you can use a
closure and do this for example:
def factory(tag):
    def generic_tag(*args, **kwargs):
        return Tag._generate_tag(tag, *args, **kwargs)
    return generic_tag
def _tag_meta(name, bases, dict_):
    for tag in ALL_TAGS:
        dict_[tag] = staticmethod(factory(tag))
    return type(name, bases, dict_)

I see - I was thinking it would preserve the closure from the for
statement, but I can see now why that would be wrong.


However, I am usure about why you are using a metaclass.

It was the easiest way I found to add a lot of static methods to the
Tag class without writing each one out. __getattr__ was not working
for this application. This is for a very simple application, and I
didn't want to add a lot of complexity to it's use. I'm always open
for other options OTOH.

This should work (untested):

class Tag(object):
@staticmethod
def _generate_tag(tag_name, *args, **kwargs):
# Does the expected, following is just for the example's sake
return tag

for tag in ALL_TAGS:
setattr(Tag, tag, staticmethod(factory(tag)))

Or you could override __getattr__
 
F

Falcolas

This should work (untested):

class Tag(object):
    @staticmethod
    def _generate_tag(tag_name, *args, **kwargs):
        # Does the expected, following is just for the example's sake
        return tag

for tag in ALL_TAGS:
    setattr(Tag, tag, staticmethod(factory(tag)))

Or you could override __getattr__

I tried overriding __getattr__ and got an error at runtime (the
instance did not have xyz key, etc), and the Tag dict is not
modifiable (granted, I tried Tag.__dict__[tag] = spam, not setattr()).
 
P

Peter Otten

Falcolas said:
I tried overriding __getattr__ and got an error at runtime (the

You can either move __getattr__() into the metaclass or instantiate the
class. I prefer the latter.

Both approaches in one example:
.... class __metaclass__(type):
.... def __getattr__(self, name): return "<%s> via metaclass" %
name
'<yadda> via class'
 
Ad

Advertisements

F

Falcolas

You can either move __getattr__() into the metaclass or instantiate the
class. I prefer the latter.

Both approaches in one example:


...     class __metaclass__(type):
...             def __getattr__(self, name): return "<%s> via metaclass" %
name
...     def __getattr__(self, name): return "<%s> via class" % name
...>>> Tag.yadda



'<yadda> via class'

Very nice, thanks!
 
Ad

Advertisements

A

Arnaud Delobelle

Falcolas said:
Or you could override __getattr__

I tried overriding __getattr__ and got an error at runtime (the
instance did not have xyz key, etc), and the Tag dict is not
modifiable (granted, I tried Tag.__dict__[tag] = spam, not setattr()).

Yes that's because __getattr__ works on instances, not on the class
itself. To achieve this, you'd have to override the *metaclass*'s
__getattr__ method.

But are you sure that you want lots of staticmethods? Why not
instanciate your class Tag instead and have normal methods to generate
tags? This would have the added benefit that you could set some options
in the __init__ method of Tag to customize its behaviour.

If you have a class which only contains static methods, you'd be better
off with a module which contains good old functions.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top