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.