【问题标题】:AttributeError: can't set attributeAttributeError:无法设置属性
【发布时间】:2026-01-17 17:25:01
【问题描述】:

我正在处理一个遗留的 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 来测试视图,而是创建了一个模拟请求并将其传递给 view as(在测试中调用视图作为可调用)如下

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'}))

【问题讨论】:

  • 你能告诉我们整个回溯错误信息吗?
  • 堆栈跟踪的最大部分只是一堆代码爆炸的测试文件的绝对路径,视图和异常起源的实际文件,但我会在这里粘贴
  • 尝试在 self.template = template 之前打印 type(template) 并告诉我打印信息。
  • print type(template) 返回 并且 print type(self.template) 返回 *** AttributeError: 'Response' object has no attribute 'templates'
  • 在我看来,self 的响应中有一个名为 template 的只读属性。你能发布sales_office_views.py模块的show()方法吗?

标签: python django unit-testing httpresponse


【解决方案1】:

这个答案没有解决这个问题的细节,但解释了根本问题。 当您尝试更改的属性实际上是没有设置器的property 时,会引发此特定异常“AttributeError:无法设置属性”(请参阅​​source)。如果您可以访问该库的代码,添加 setter 即可解决问题。

编辑:更新源链接到代码中的新位置。

【讨论】:

  • 我想这应该被认为是一项成就,我的答案是负分,而其他所有的都是零。我“绝对”更好。 :)
  • "source" 链接是 404,但是在当前代码中我找到了属性here as python,或者作为actual C code
  • 是否可以拥有没有setter的属性?
  • @StevenVascellaro,当然,这正是问题中的问题。除非明确提供了 setter,否则属性是只读的。
  • @CharlieParker,总的来说你是对的,但在这种情况下,通过使用 @property 装饰器,库的开发人员有意限制了设置该属性的能力。这是一个类似用例的相关问题 - softwareengineering.stackexchange.com/questions/371540/…
【解决方案2】:

您似乎没有在 Response 类中使用 self.template。试试这样:

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

【讨论】:

  • 是的 self.template 没有在 Response 类内部使用,它用于这样的断言测试; self.assertEquals('some_template.html', response.template) 和calling_context一样
  • 整个响应类都可以在测试中使用
  • @austiine 那么删掉两行后,还是一样的错误吗?
  • 没有错误消失,但我的测试失败了,因为它们除了响应具有模板和调用上下文属性
【解决方案3】:

我查看了 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 构造将patch 用作上下文管理器,但装饰器是更简洁的方法。我不知道Response 是否只是一个用于测试的存根类,但在这种情况下,您可以直接修补HttpResponce,但这取决于您的代码。

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

【讨论】: