是否可以使用python装饰器更改类(类型)的名称?

时间:2014-02-18 18:49:47

标签: python django class python-decorators

我正在尝试通过装饰我的Django(ORM)模型(类定义)来自动编码100个数据库表模型,以从文件名中派生出它们的类名。但我认为我的“装饰深度”太浅了。我的def方法中是否需要函数__call__或类定义?是不是可以用这样简单的东西来完成?

# 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

装饰器的示例用法

# 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)

模型定义中不存在具有新名称的模型,如果不查看装饰器类(而不是模型类)来查找属性,我就无法使用它!

>>> 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__'

我是否需要以某种方式装饰模块?

2 个答案:

答案 0 :(得分:3)

您正在使用类本身作为装饰器,因此只调用其__init__。您的__call__永远不会被调用。

改为使用实例:

@decorators.prefix_model_name_with_filename()

关于您更新的问题:装饰器无法更改用于引用封闭命名空间中的对象的实际名称。 (您可以通过将新名称粘贴到特定名称空间来强制它,但是您可以确保将名称放入的名称空间是最初定义对象的名称空间。)装饰器语法:

@deco
class Foo(object):
    ...

相当于

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

请注意,最后一行显示为Foo =,因为原始类是使用class Foo定义的。你不能改变它。装饰对象始终重新分配到它原来的名称。

有各种各样的方法来解决这个问题。您可以让装饰器为全局命名空间写一个新名称,或者甚至查看装饰对象的__module__属性并为该命名空间写一个新名称。然而,这并不是装饰器的用途,而是会让你的代码混乱。装饰器用于修改/包装对象,而不是修改从中访问这些对象的名称空间。

答案 1 :(得分:2)

也许这是元类的工作:

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__

前面的示例只是创建了名称已更改的新类,但是如果您希望模块能够反映您需要的更改(请注意我的python脚本名为 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

输出:

untitled0A
<class '__main__.untitled0A'>