【问题标题】:Is it possible to change the name of a class (type) with a python decorator?是否可以使用 python 装饰器更改类(类型)的名称?
【发布时间】:2014-03-18 17:40:38
【问题描述】:

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

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

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

【问题讨论】:

    标签: python django class python-decorators


    【解决方案1】:

    您将类本身用作装饰器,因此仅调用其__init__。你的__call__ 永远不会被调用。

    改用实例:

    @decorators.prefix_model_name_with_filename()
    

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

    @deco
    class Foo(object):
        ...
    

    相当于

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

    注意最后一行是Foo =,因为原来的类是用class Foo定义的。你无法改变这一点。被装饰的对象总是被重新分配给它原来的名字。

    有一些骇人听闻的方法可以解决这个问题。你可以让你的装饰器给全局命名空间写一个新名字,或者甚至查看被装饰对象的__module__ 属性,然后给那个命名空间写一个新名字。然而,这并不是装饰器的真正用途,它会让你的代码变得混乱。装饰器用于修改/包装对象,而不是修改访问这些对象的命名空间。

    【讨论】:

    • 这解决了我的直接错误。 ShipMeth.__name__ 现在正在工作(适当的前缀)。但是它不会影响模块的 locals() dict 键(类/类型的实际名称)。所以我仍然缺少一些东西。
    • 我需要以某种方式装饰模块吗?
    • @hobs:查看我的更新答案。你不能用装饰器做到这一点(没有丑陋的黑客)。
    • 我想我只会让编写类定义的自动编码器将正确的名称放入 py 文件中,而不是在所有类前面加上装饰器。事后看来,装饰者的尝试似乎过于复杂。感谢您提醒/教育装饰器是什么及其局限性。
    【解决方案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'>
    

    【讨论】:

    • 可能,但我不知道如何使用__metaclass__ 方法来帮助我“实例化”100 多个类(具有唯一名称)。
    • 嗯,你能像我发布的例子那样做吗?我的意思是:类class ShipMeth(models.Model): \n __metaclass__ = cls_changer
    • 啊,我明白了。会试试的。
    • 好吧,我刚刚注意到您使用 Django!也许这种方法对您没有帮助,因为 Django 也使用元类!所以如果你改变它,你可能会搞砸!试着让我知道;)
    • 我找到了解决您的问题的方法,检查答案的编辑
    猜你喜欢
    • 2016-10-09
    • 2012-09-03
    • 2022-08-15
    • 2018-08-31
    • 2015-06-04
    • 2020-03-10
    • 1970-01-01
    • 2020-08-16
    • 1970-01-01
    相关资源
    最近更新 更多