-1

I have a metaclass that just append a prefix to the classes on my modules. So far so good, the problem is that I want to remove the definition of the class that is calling the metaclass. For example if class A is prefixed to PrefixA I will end with both classes on my globals but I just want PrefixA on it. For now to achieve my goal I'm doing something like this:

class PrefixChanger(type):

    prefix = 'Prefix'
    to_delete = []

    def __new__(cls, clsname, bases, attrs):
        new_cls = type.__new__(cls,cls.prefix+clsname,bases,attrs)
        globals()[cls.prefix+clsname] = new_cls
        cls.to_delete.append(clsname)
        return new_cls

    @classmethod
    def do_clean(cls):
        gl = globals() 
        for name in cls.to_delete:            
            del gl[name]                

class A(object):
    __metaclass__ = PrefixChanger        

class B(PrefixA):
    pass

PrefixChanger.do_clean()

The problem with this approach is that I have to put the last line PrefixChanger.do_clean() at the end of every module that uses my metaclass.

So, there is any way to do this the right way? I mean that the metaclass (or some kind of black magic) deletes the unwanted classes from the globals as soon as possible (maybe right after theirs definitions).

I'm interested on complete the task this way but some suggestions for doing it using another approach are welcome too.

Also my solution breaks on this case:

class B(object):
    class C(object):
        __metaclass__ = PrefixChanger

Extra credits to the one that solves it!

martineau
  • 119,623
  • 25
  • 170
  • 301
Alvaro Fuentes
  • 16,937
  • 4
  • 56
  • 68
  • 4
    This is a terrible idea on so many levels.. – Martijn Pieters Feb 20 '14 at 20:16
  • Humm, I think you are right, this is weird stuff, but ... is there a solution? – Alvaro Fuentes Feb 20 '14 at 20:20
  • 1
    @MartijnPieters Elaborate, I'm interested in what can go wrong with this kind of idea (unless you just mean that using a metaclass simply to add a prefix to the name of the classes using that metaclass is very silly and it would be simpler to just have a module containing the classes with the prefix as its name). – JAB Feb 20 '14 at 20:20
  • The principle of least surprise, for one. Discoverability, self-documentation, etc., all are violated here. – Martijn Pieters Feb 20 '14 at 20:22
  • @JAB: That's what I'd do; the pain you have to go through to provide that prefix, provide new bindings and clear the original name from the globals is, to me, not worth it, silly, too much work for little gain, complicates the code base unnecessarily, makes it harder to read the code (where did `PrefixA` appear from?), makes it harder to use code linting tools, etc. – Martijn Pieters Feb 20 '14 at 20:27

2 Answers2

0

Possibility 3

Start a thread with a queue blocking and deleting classes from their referrers. You can obtain the referencing objects using the gc module. gc.get_referrers

Muhaha

### License: You may only use this code if it is for the good of humanity.
### Any copy of the code requires this license to be copied with it.

>>> class T(type):
    prefix = 'meep'
    def __new__(cls, name, bases, attrs):
        new_cls = type.__new__(cls, cls.prefix+name,bases,attrs)
        globals()[cls.prefix+name] = new_cls # interesting
        l = []
        import threading
        def t():
            import gc
            while 1:
                for ref in gc.get_referrers(l):
                    try: del ref[name];break
                    except:pass
        threading.Thread(target = t).start()
        return l


>>> class C(object):
    __metaclass__ = T


>>> C

Traceback (most recent call last):
  File "<pyshell#30>", line 1, in <module>
    C
NameError: name 'C' is not defined
>>> meepC
<class '__main__.meepC'>

Impossibility 2

Do not put a class in it then you do not need to delete it:

class PrefixChanger(type):
    # ...

    def __new__(cls, clsname, bases, attrs):
        # ...
        return None

Impossibility 1 How about

PrefixChanger('A', (object,), {})

You do not need to delete it. I mean.. Your classes do not have content but if there should be then this is not an option of course.

User
  • 14,131
  • 2
  • 40
  • 59
  • Yes, for the example it works, but as you said, I want to use it on other classes that do have content – Alvaro Fuentes Feb 20 '14 at 20:55
  • The **Impossibility 2** was my first thought but as the question said I don't want to put `None` on the globals I just want to drop the class. This approach will cause even more errors like the ones @MartijnPieters pointed. – Alvaro Fuentes Feb 20 '14 at 21:05
  • **Impossibility 3** is really **impossible** because the idea is to use the modules with `PrefixChanger` as a normal module (actually in my approach it is working, you can import the module from another and everything is ok, even the code completion works fine on my IDE) – Alvaro Fuentes Feb 20 '14 at 21:09
  • no. it is possible. I will rename it.. Just do it. :) – User Feb 20 '14 at 21:10
  • I hope you have something good in mind with a language that has treated you so gently. But a nice weird task! – User Feb 20 '14 at 21:23
0

The most straightforward way to delete them right after creation is to do it right after creation — there don't seem to be any class or module creation "hooks" (software intercepts) that provide a way enable implicitly do this.

The following then is a trade-off:  It eliminates the need to call ado_clean() function at the end of every module, instead however a call to a function has to be placed after every class definition.
On the other hand, that's probably not any worse than having to add a

    __metaclass__ = PrefixChanger

statement in every class definition...

import textwrap  # to help make the exec code more readable

def Prefix(cls, prefix='Prefix'):
    exec(textwrap.dedent('''\
        global {prefix}{classname}
        {prefix}{classname} = {classname}
        {prefix}{classname}.__name__ = "{prefix}{classname}"
        del globals()["{classname}"]
        '''.format(prefix=prefix, classname=cls.__name__))
    )

class A(object):
    pass
Prefix(A)

class B(PrefixA):
    pass
Prefix(B)

if __name__ == '__main__':
    try:
        print 'A:', A
    except NameError:
        print 'there\'s nothing named "A"'
    else:
        print 'error: there shouldn\'t be anything named "A"'

    print 'PrefixA:', PrefixA
    print 'PrefixB:', PrefixB

Output:

A: there's nothing named "A"
PrefixA: <class '__main__.PrefixA'>
PrefixB: <class '__main__.PrefixB'>
martineau
  • 119,623
  • 25
  • 170
  • 301