属性错误: 无法设置属性。

34

我正在处理一个传统的 Django 项目,其中有一个类定义如下:

from django.http import HttpResponse

class Response(HttpResponse):
    def __init__(self, template='', calling_context='' status=None):
        self.template = template
        self.calling_context = calling_context
        HttpResponse.__init__(self, get_template(template).render(calling_context), status)

这个类在视图中的使用方式如下:

def some_view(request):
    #do some stuff
    return Response('some_template.html', RequestContext(request, {'some keys': 'some values'}))

这个类主要是为了在单元测试中执行断言而创建的。即,他们不使用django.test.Client来测试视图,而是创建一个模拟请求,并将其作为可调用对象传递给视图,在测试中调用视图,具体如下:

def test_for_some_view(self):
    mock_request = create_a_mock_request()
    #call the view, as a function
    response = some_view(mock_request) #returns an instance of the response class above
    self.assertEquals('some_template.html', response.template)
    self.assertEquals({}, response.context)

问题在于,在测试套件执行到一半(一个相当庞大的测试套件),一些测试开始在执行时崩溃。
return Response('some_template.html', RequestContext(request, {'some keys': 'some values'}))

堆栈跟踪信息如下:

self.template = template
AttributeError: can't set attribute 

完整的堆栈跟踪看起来像这样:
======================================================================
ERROR: test_should_list_all_users_for_that_specific_sales_office
 ----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/austiine/Projects/mped/console/metrics/tests/unit/views/sales_office_views_test.py",   line 106, in test_should_list_all_users_for_that_specific_sales_office
    response = show(request, sales_office_id=sales_office.id)
File "/Users/austiine/Projects/mped/console/metrics/views/sales_office_views.py", line 63, in show
    "sales_office_users": sales_office_users}))
File "/Users/austiine/Projects/mped/console/metrics/utils/response.py", line 9, in __init__
    self.template = template
    AttributeError: can't set attribute

实际失败的测试是

def test_should_list_all_users_for_that_specific_sales_office(self):
    user_company = CompanyFactory.create()
    request = self.mock_request(user_company)
    #some other stuff

    #calling the view
    response = show(request, sales_office_id=sales_office.id)
    self.assertIn(user, response.calling_context["sales_office_users"])
    self.assertNotIn(user2, response.calling_context["sales_office_users"])

展示视图的代码

def show(request, sales_office_id):
    user = request.user
    sales_office = []
    sales_office_users = []
    associated_market_names = []
    try:
        sales_office = SalesOffice.objects.get(id=sales_office_id)
        sales_office_users = User.objects.filter(userprofile__sales_office=sales_office)
        associated_market_names = Market.objects.filter(id__in=           (sales_office.associated_markets.all())).values_list("name", flat=True)
        if user.groups.all()[0].name == UserProfile.COMPANY_AO:
            associated_market_names = [market.name for market in sales_office.get_sales_office_user_specific_markets(user)]
        except:
            pass
    return Response("sales_office/show.html", RequestContext(request, {'keys': 'values'}))

堆栈跟踪的最大部分只是一堆测试文件的绝对路径,这些文件是代码崩溃的地方,视图和实际文件是异常发生的地方,但我会在这里粘贴。 - austiine
@Micheled'Amico添加了在底部显示的代码。 - austiine
repr(type(self).dict) 将显示 Request 实例的属性。'template' 是否是其中之一? - XORcist
@XORcist 不,当我将pdb作为__init__方法的第一行并执行该行时,模板不存在,我会得到"['module', 'doc', 'init']",而self.__dict__是{}。 - austiine
可能我没有澄清的一件事是,有些使用响应类的视图测试通过了,而其他一些却失败了,这就很奇怪。 - austiine
显示剩余5条评论
3个回答

116
这个回答没有解决这个问题的具体内容,但是解释了其中的基本问题。当你试图更改的属性实际上是一个没有setter的property时,就会引发特定的异常"AttributeError: can't set attribute"(请参见source)。如果你可以访问库的代码,则添加setter将解决该问题。
编辑:将源链接更新为代码中的新位置。
编辑2:
setter的示例:
class MAMLMetaLearner(nn.Module):
    def __init__(
            self,
            args,
            base_model,

            inner_debug=False,
            target_type='classification'
    ):
        super().__init__()
        self.args = args  # args for experiment
        self.base_model = base_model
        assert base_model is args.model

        self.inner_debug = inner_debug
        self.target_type = target_type

    @property
    def lr_inner(self) -> float:
        return self.args.inner_lr

    @lr_inner.setter
    def lr_inner(self, new_val: float):
        self.args.inner_lr = new_val

11
我想应该算是一种成就,因为我的回答得到了负分,而其他人的得分都是零。我“绝对”更好。 :) - yoniLavi
1
"source"链接是404,但在当前代码中,我发现属性在这里作为Python,或者作为实际的C代码 - luckydonald
@StevenVascellaro,没错,这正是问题所在。除非显式提供setter,否则属性是只读的。 - yoniLavi
2
@CharlieParker,你的观点总体上是正确的,但在这种情况下,通过使用@property装饰器,库的开发人员有意限制了设置该属性的能力。以下是一个类似用例的相关问题 - https://softwareengineering.stackexchange.com/questions/371540/should-a-python-property-decorator-be-used-without-a-setter-or-deleter - yoniLavi
1
@yoniLavi那些愚蠢的开发者。他们为什么要这样做!?(提示:是我干的... :/,现在我在想为什么我这样做以及如何修复它...) - Charlie Parker
显示剩余3条评论

0

看起来你在 Response 类中没有使用 self.template。尝试像这样:

class Response(HttpResponse):
    def __init__(self, template='', calling_context='' status=None):
        HttpResponse.__init__(self, get_template(template).render(calling_context), status)

是的,在Response类中没有使用self.template,它在测试中用于像这样的断言:self.assertEquals('some_template.html', response.template) 调用上下文也是一样的。 - austiine
整个响应类可用于测试。 - austiine
@austiine 在删除了两行代码之后,错误还是一样的吗? - Stephen Lin
不,那个错误已经消失了,但我的测试失败了,因为它们期望响应具有模板和调用上下文属性。 - austiine

0

我看了一下 Django 的源代码,不知道 HttpResponse 中的 templatetemplates 属性是从哪里来的。但我可以建议您改变测试方法,并迁移到 mock 框架。您可以像这样重写您的测试:

@patch("qualified_path_of_response_module.response.Response", spec=Response)
def test_should_list_all_users_for_that_specific_sales_office(self,mock_resp):
    user_company = CompanyFactory.create()
    request = self.mock_request(user_company)
    #some other stuff

    #calling the view
    response = show(request, sales_office_id=sales_office.id)
    self.assertTrue(mock_resp.called)
    context = mock_resp.call_args[0][2]
    self.assertIn(user, context["sales_office_users"])
    self.assertNotIn(user2, context["sales_office_users"])

@patch 装饰器将您的 Response() 类替换为 MagicMock() 并将其作为 mock_resp 变量传递给测试方法。您还可以使用 with 构造作为上下文管理器,但装饰器是更干净的方法。我不知道 Response 是否只是用于测试的存根类,但在这种情况下,您可以直接修补 HttpResponce,但这取决于您的代码。

您可以在 这里 找到有关 call_args 的详细信息。也许您需要使用 spec 属性,因为 Django 进行了一些类型检查... 但是尝试使用和不使用它(我不是 Django 专家)。探索 mock 框架:它将为您提供许多强大的工具来进行简单的测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接