سؤال

I'm trying to autocode 100's of database table models by decorating my Django (ORM) models (class definitions) to derive their class names from the file name. But I think my "depth of decoration" is too shallow. Do I need a function def or class definition within my __call__ method? Can it not be done with something simple like this?

# decorators.py
import os
from inspect import get_module

class prefix_model_name_with_filename(object):
    'Decorator to prefix the class __name__ with the *.pyc file name'

    def __init__(self, sep=None):
        self.sep = sep or ''

    def __call__(self, cls):
        model_name = os.path.basename(getmodule(cls).__file__).split('.')[0]
        setattr(cls, '__name__', model_name + self.sep + getattr(cls, '__name__'))
        return cls

Example usage of the decorator

# models.py
from django.db import models
import decorators

@decorators.prefix_model_name_with_filename
class ShipMeth(models.Model):
    ship_meth = models.CharField(max_length=1, primary_key=True)

The model doesn't exist in the module definition with the new name and I can't use it without it looking in the decorator class (rather than the model class) to find attributes!

>>> from sec_mirror.models import ShipMeth
>>> ShipMeth.__name__

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/home/Hobson/.virtualenvs/dev/lib/python2.7/site-packages/django/core/management/commands/shell.pyc in <module>()
----> 1 ShipMeth.__name__

AttributeError: 'prefix_model_name_with_filename' object has no attribute '__name__'

Do I need to decorate the module somehow?

هل كانت مفيدة؟

المحلول 2

Maybe that's the work for a metaclass:

import os

def cls_changer(name, parents, attrs):
    model_name = os.path.basename(__file__).split('.')[0]
    return type(model_name+name, parents, attrs)

class A(object):
    __metaclass__ = cls_changer
    pass

print A.__name__

The previous example just create new classes with the name changed but if you want your module to reflect the changes you need to this (Note my python script is named untitled0.py):

import os

def cls_changer(name, parents, attrs):
    model_name = os.path.basename(__file__).split('.')[0]
    res = type(model_name+name, parents, attrs)
    gl = globals()
    gl[model_name+name] = res
    return res

class A(object):
    __metaclass__ = cls_changer
    pass

print A.__name__
print untitled0A

Output:

untitled0A
<class '__main__.untitled0A'>

نصائح أخرى

You're using the class itself as the decorator, so only its __init__ is called. Your __call__ is never called.

Use an instance instead:

@decorators.prefix_model_name_with_filename()

About your updated question: A decorator can't change the actual name used to refer to an object in the enclosing namespace. (You can brute-force it by sticking a new name into a particular namespace, but it's up to you to ensure that the namespace you put the name into is the one where the object was originally defined.) The decorator syntax:

@deco
class Foo(object):
    ...

Is equivalent to

class Foo(object):
    ...
Foo = deco(Foo)

Note that the last line says Foo =, because the original class was defined with class Foo. You can't change that. The decorated object is always reassigned to the same name it originally had.

There are hackish ways to get around this. You can make your decorator write a new name to the global namespace, or even look at the decorated object's __module__ attribute and write a new name to that namespace. However, this isn't really what decorators are for, and it's going to make your code confusing. Decorators are for modifying/wrapping objects, not modifying the namespaces from which those objects are accessed.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top