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.
CodePudding user response:
__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__.
CodePudding user response:
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.
CodePudding user response:
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.
