How do I resolve AttributeError: "Pages" object has no attribute "pages"

Question:

I have a few custom classes that look like this:

from typing import List
from typing_extensions import Self

class Page:
    def __init__(self, search_id: str, page_num: int) -> None:
        self.search_id = search_id
        self.page_num = page_num
        self.isLast = False

    def mark_as_last(self):
        self.isLast = True
class Pages:
    def __new__(cls: Self, search_id: str, range_of_pages: List[int]):
        instance = super(Pages, cls).__new__(cls)
        return instance.pages

    def __init__(self, search_id: str, range_of_pages: List[int]):
        self.search_id = search_id
        self.ranges_of_pages = range_of_pages
        self.pages = Pages.create_pages(self.ranges_of_pages, self.search_id)

    @staticmethod
    def create_pages(range_of_pages: List[int], search_id: str) -> List[Page]:
        pages = []
        for page_num in range_of_pages:
            page = Page(search_id, page_num)
            if page_num == range_of_pages[-1]:
                page.mark_as_last()
            pages.append(page)
        return pages

    def __getitem__(self, item):
        return self.pages[item]

When ‘Pages’ is called like Pages('123', [1, 2, 3, 4]), I want to return a list of pages – see return instance.pages

Well… when I get to this point, I get an error. Specifically this error:

def __new__(cls: Self, search_id: str, range_of_pages: List[int]):
        instance = super(Pages, cls).__new__(cls)
        return instance.pages
E       AttributeError: 'Pages' object has no attribute 'pages'

Am I missing something? This should work. I have no idea what is wrong here.

Asked By: prismo

||

Answers:

__new__ handles the creation of a class instance, whereas __init__ initializes it. Since __new__ runs before __init__, the created instance does not yet have the pages attribute which is assigned in __init__.

Answered By: Fractalism

The issue here is that the new method is called before the init method, so the pages attribute is not yet defined when you try to access it in the new method.

Instead of returning instance.pages from the new method, you should return the instance itself, and then the init method will be called, where you can initialize the pages attribute.

Answered By: RASFI

I don’t think it is a good idea to return an object from __new__ that is of a different type than the class, but it is possible (see e.g. Can the constructor of a class return another class in Python?).

In your case you were trying to access and return the .pages attribute of the newly created Pages object before it was initialized in __init__, which is called after __new__.

Since __new__ depends on everything that currently happens in __init__, you have to move everything into __new__.

class Pages:
    def __new__(cls: Self, search_id: str, range_of_pages: List[int]):
        instance = super(Pages, cls).__new__(cls)
        instance.search_id = search_id
        instance.ranges_of_pages = range_of_pages
        return Pages.create_pages(ranges_of_pages, search_id)

But that really makes me wonder why this class is needed at all. The search_id and ranges_of_pages attributes do not seem to be needed anywhere else (in fact they can’t be, because the Pages instance is immediately discarded), so you could just remove the class and make create_pages a free function.

Answered By: mkrieger1
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.