4

I'm writing a python (3.2+) plugin library and I want to create a function which will create some variables automatically handled from config files.

The use case is as follows (class variable):

class X:
    option(y=0)
    def __init__(self):
        pass

(instance variable):

class Y:
    def __init__(self):
        option(y=0)

the option draft code is as follows:

def option(**kwargs):
    frame   = inspect.stack()[1][0]
    locals_ = frame.f_locals
    if locals_ == frame.f_globals:
        raise SyntaxError('option() can only be used in a class definition')

    if '__module__' in locals_:
        # TODO

    else:
        for name, value in kwargs.items():
            if not name in locals_["self"].__class__.__dict__:
                setattr(locals_["self"].__class__, name, VirtualOption('_'+name, static=False))
            setattr(locals_["self"], '_'+name,value)

I have problem the first case, when option is declared as class variable. Is it possible to somehow get reference to the class in which this function was used (in example to class X)?

Wojciech Danilo
  • 11,573
  • 17
  • 66
  • 132

2 Answers2

2

You cannot get a reference to the class, because the class has yet to be created. Your parent frame points a temporary function, whose locals() when it completes will be used as the class body.

As such, all you need to do is add your variables to the parent frame locals, and these will be added to the class when class construction is finished.

Short demo:

>>> def foo():
...     import sys
...     flocals = sys._getframe(1).f_locals
...     flocals['ham'] = 'eggs'
... 
>>> class Bar:
...     foo()
... 
>>> dir(Bar)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__locals__', '__lt__', '__module__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'ham']
>>> Bar.ham
'eggs'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • This is a very sensible answer, but is this really **guaranteed** to be the way it works by the data model? (The answer may be yes -- This is deeper into the inner workings of python than I need to explore most of the time) – mgilson Jan 04 '13 at 16:38
  • @mgilson: yes, it is. You can check the bytecode of a function constructing a class + the `ceval.c` loop if you really care, but that's how it works. – Martijn Pieters Jan 04 '13 at 16:40
  • I suppose I'm thinking of other python implementations (`Jython`, `pypy` ...) where `ceval.c` may be completely irrelevant ... – mgilson Jan 04 '13 at 16:41
  • @mgilson: it's part of the [python specification](http://docs.python.org/3/reference/compound_stmts.html#class-definitions). *The class’s suite is then executed in a new execution frame*. – Martijn Pieters Jan 04 '13 at 16:42
  • Thank you, I knew about it, but I wanted to get the reference to the class. You have told, that the class is not yet created, so it is impossible. Thank you :) – Wojciech Danilo Jan 04 '13 at 16:44
  • @danilo2: To get a reference to the class, use a metaclass or a class decorator instead. You cannot get it from inside the class suite (at least not without a lot of hoops, you can set `__metaclass__` in the calling frame and thus hook in to the metaclass process, but that gets very ugly fast indeed). – Martijn Pieters Jan 04 '13 at 16:49
  • Thank you, I'll not use it, but could you please tell a little more what you mean by "set `__metaclass__` in the calling frame and thus hook in to the metaclass process"? – Wojciech Danilo Jan 04 '13 at 17:04
  • @danilo2: see [How does the function that is called inside the class declaration?](http://stackoverflow.com/questions/13323146/13330660#13330660) and the source code of `zope.interface` if you really want to know. – Martijn Pieters Jan 04 '13 at 17:12
  • @MartijnPieters thanks! But I see that this will notwork in Python3+ since the `locals_['__metaclass__']` does nothing in "modern" Python :) – Wojciech Danilo Jan 04 '13 at 23:05
  • @danilo2: Indeed, and this is why `zope.interface` throws a warning when you use the old `implements(IInterface)` spelling in Python 3. – Martijn Pieters Jan 05 '13 at 10:08
0

It seems to me that a metaclass would be suitable here:

python2.x syntax

def class_maker(name,bases,dict_):
    dict_['y']=0
    return type(name,bases,dict_)

class X(object):
    __metaclass__ = class_maker
    def __init__(self):
        pass

print X.y
foo = X()
print foo.y

python3.x syntax

It seems that python3 uses a metaclass keyword in the class definition:

def class_maker(name,bases,dict_):
    dict_['y']=0
    return type(name,bases,dict_)

class X(metaclass=class_maker):
    def __init__(self):
        pass

print( X.y )
foo = X()
print( foo.y )
print( type(foo) )

Or, more along the lines of what you have in your question:

def class_maker(name,bases,dict_,**kwargs):
    dict_.update(kwargs)
    return type(name,bases,dict_)

class X(metaclass=lambda *args: class_maker(*args,y=0)):
    def __init__(self):
        pass

print( X.y )
foo = X()
print( foo.y )
print( type(foo) )
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • I don't think that using a metaclass will handle the instance usecase correctly, at least not without wrapping or otherwise hacking at `__init__` within the metaclass as well. I was thinking some sort of descriptor might do the trick, especially since he wants to be able to access the class from the calling context. – Silas Ray Jan 04 '13 at 16:26
  • @sr2222 -- the instance use case seems very convoluted to me. Why not create a base class with an `self._option(**kwargs)` method and just use `setattr` in there? – mgilson Jan 04 '13 at 16:28
  • Quite so, though I suspect "why not just" may be a long rabbit hole to travel here. – Silas Ray Jan 04 '13 at 16:29
  • @sr2222 -- You're probably right about that. Anyway, I don't know much about metaclasses -- I think this is the first time I've ever even made an attempt to answer a question using one ... This is sort of a learning experience for me :) – mgilson Jan 04 '13 at 16:32
  • No, it will not be suitable. As sr2222 told, there are some special conditions why I dont want to use metaclass, and yes, metaclasses in Python3+ are handled in other way (as keyword argument to the class definition) – Wojciech Danilo Jan 04 '13 at 16:46