2012-02-20 53 views
8

Hãy để tôi bắt đầu với điều này không phải là lặp lại của Why does __init__ not get called if __new__ called with no args. Tôi đã cố gắng xây dựng cẩn thận một số mã mẫu cho __new____init__ mà không có lời giải thích nào tôi có thể tìm thấy.Tại sao __init__ không được gọi sau __new__ SOMETIMES

Các thông số cơ bản:

  • Có một lớp cơ sở gọi là NotMine vì nó xuất phát từ một thư viện (Tôi sẽ tiết lộ ở cuối, không quan trọng ở đây)
  • lớp đó có một phương pháp __init__ rằng lần lượt gọi một phương pháp _parse
  • tôi cần phải ghi đè lên các phương pháp _parse trong lớp con
  • mà lớp con tôi là tạo ra không biết cho đến khi gọi
  • Tôi biết có những phương pháp thiết kế nhà máy nhưng tôi không thể sử dụng chúng ở đây (Xem thêm ở cuối)
  • Tôi đã cố gắng để tận dụng cẩn thận của super để tránh các vấn đề trong Python logging: Why is __init__ called twice?
  • Tôi biết điều này cũng là 'loại 'cơ hội AbstractBaseMehtod nhưng điều đó không giúp

Dù sao, __init__ nên được gọi sau __new__ và cho mọi giải thích về lý do tại sao một số mẫu dưới đây không làm việc tôi dường như để có thể trỏ đến các trường hợp khác mà làm việc và loại trừ lời giải thích.

class NotMine(object): 

    def __init__(self, *args, **kwargs): 
     print "NotMine __init__" 
     self._parse() 

    def _parse(self): 
     print "NotMine _parse" 

class ABC(NotMine): 
    def __new__(cls,name,*args, **kwargs): 
     print "-"*80 
     print "Entered through the front door ABC.__new__(%s,%s,*%s,**%s)"%(cls,name,args,kwargs) 
     if name == 'AA': 
      obj = super(NotMine,ABC).__new__(AA,*args,**kwargs) 
      print "Exiting door number 1 with an instance of: %s"%type(obj) 
      return obj 
     elif name == 'BB': 
      obj = super(NotMine,ABC).__new__(BB,*args,**kwargs) 
      print "Exiting door number 2 with an instance of: %s"%type(obj) 
      return obj 
     else: 
      obj = super(NotMine,ABC).__new__(cls,*args,**kwargs) 
      print "Exiting door number 3 with an instance of: %s"%type(obj) 
      return obj 

class AA(ABC): 

    def _parse(self): 
     print "AA _parse" 

class BB(ABC): 

    def __init__(self, *args, **kw): 
     print "BB_init:*%s, **%s"%(args,kw)   
     super(BB,self).__init__(self,*args,**kw) 

    def _parse(self): 
     print "BB _parse" 

class CCC(AA): 

    def _parse(self): 
     print "CCCC _parse" 


print("########### Starting with ABC always calls __init__ ############") 
ABC("AA")   # case 1 
ABC("BB")   # case 2 
ABC("NOT_AA_OR_BB") # case 3 

print("########### These also all call __init__ ############") 
AA("AA")   # case 4 
BB("BB")   # case 5 
AA("NOT_AA_OR_BB") # case 6 
BB("NOT_AA_OR_BB") # case 7 
CCC("ANYTHING") # case 8 

print("########### WHY DO THESE NOT CALL __init__ ############") 
AA("BB") # case 9 
BB("AA") # case 10 
CCC("BB") # case 11 

Nếu bạn thực thi mã, bạn có thể thấy rằng đối với mỗi cuộc gọi đến __new__ nó thông báo "mà cửa" nó được thoát qua và với những gì loại. Tôi có thể thoát khỏi cùng một "cửa" với cùng một loại "" đối tượng và có __init__ được gọi là trong một trường hợp và không phải là khác. Tôi đã nhìn vào mro của lớp "gọi" và cung cấp không có cái nhìn sâu sắc kể từ khi tôi có thể gọi lớp đó (hoặc một subcass như trong CCC) và có __init__ gọi.

End Ghi chú: Các NotMine thư viện Tôi đang sử dụng là Genshi MarkupTemplate và lý do không sử dụng một phương pháp thiết kế Nhà máy là TemplateLoader họ cần một defaultClass để xây dựng. Tôi không biết cho đến khi tôi bắt đầu phân tích cú pháp, điều tôi làm trong __new__. Có rất nhiều ma thuật voodoo mát mẻ mà genshi bộ nạp và các mẫu làm điều đó có giá trị nỗ lực này.

Tôi có thể chạy một phiên bản chưa được sửa đổi của trình tải của họ và hiện tại mọi thứ đều hoạt động miễn là tôi CHỈ vượt qua lớp ABC (lớp sắp xếp theo kiểu trừu tượng) làm mặc định. Mọi thứ đang hoạt động tốt nhưng hành vi không giải thích được này là một lỗi gần như chắc chắn sau đó.

UPDATE: Ignacio, đóng đinh câu hỏi dòng trên cùng, nếu đối tượng quay trở lại không phải là một "thể hiện của" cls sau đó __init__ không được gọi. Tôi thấy rằng gọi là "constructor" (ví dụ như AA(args..) là sai vì nó sẽ gọi __new__ một lần nữa và bạn quay lại ngay nơi bạn bắt đầu. Bạn có thể sửa đổi một arg để có một con đường khác. Điều đó có nghĩa là bạn gọi ABC.__new__ hai lần thay vì vô hạn .Giải pháp làm việc là chỉnh sửa class ABC ở trên là:

class ABC(NotMine): 
    def __new__(cls,name,*args, **kwargs): 
    print "-"*80 
    print "Entered through the front door ABC.__new__(%s,%s,*%s,**%s)"%(cls,name,args,kwargs) 
    if name == 'AA': 
     obj = super(NotMine,ABC).__new__(AA,*args,**kwargs) 
     print "Exiting door number 1 with an instance of: %s"%type(obj) 
    elif name == 'BB': 
     obj = super(NotMine,ABC).__new__(BB,*args,**kwargs) 
     print "Exiting door number 2 with an instance of: %s"%type(obj) 
    elif name == 'CCC': 
     obj = super(NotMine,ABC).__new__(CCC,*args,**kwargs) 
     print "Exiting door number 3 with an instance of: %s"%type(obj) 
    else: 
     obj = super(NotMine,ABC).__new__(cls,*args,**kwargs) 
     print "Exiting door number 4 with an instance of: %s"%type(obj) 
    ## Addition to decide who calls __init__ ## 
    if isinstance(obj,cls): 
     print "this IS an instance of %s So call your own dam __init__"%cls 
     return obj 
    print "this is NOT an instance of %s So __new__ will call __init__ for you"%cls 
    obj.__init__(name,*args, **kwargs) 
    return obj 

print("########### now, these DO CALL __init__ ############") 
AA("BB") # case 9 
BB("AA") # case 10 
CCC("BB") # case 11 

Lưu ý một vài dòng cuối cùng. Không gọi __init__ nếu đó là một lớp "khác" không có ý nghĩa với tôi, ĐẶC BIỆT khi lớp "khác" vẫn là lớp con của lớp gọi là __init__. Tôi không thích các chỉnh sửa ở trên nhưng ít nhất tôi nhận được các quy tắc tốt hơn một chút ngay bây giờ.

+0

Genshi có sử dụng metaclass không? Xem http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python – Borealid

+0

Không, Mã mẫu của tôi không sử dụng genshi làm cơ sở. –

Trả lời

7

Từ the documentation:

Nếu __new__() không trả lại một thể hiện của cls, sau đó phương pháp thể hiện mới của __init__() sẽ không được gọi.

này là cho phép __new__()-return a new instance of a different class, trong đó có riêng của mình __init__() được gọi để thay thế. Bạn sẽ cần phải phát hiện nếu bạn đang tạo một cls mới, và gọi hàm tạo thích hợp thay vào đó nếu không.

+0

OK, tôi loại siêu chữ "không trả lại một thể hiện của các cl". Nhưng, tôi đang trả về một thể hiện mới của một lớp khác, mà nó có riêng '__init__' nhưng nó không được gọi, ngay cả khi nó là một cơ sở chung (trong mro cũ). Am ** I ** phải gọi nó là mới? –

+0

Bạn được phép * khởi tạo * nó trong '__new __()', bằng cách gọi hàm tạo của nó. –

+0

ouch. Ý tưởng tồi ở đây. Tôi nghĩ rằng điều này có thể dẫn đến vấn đề "được gọi hai lần" mà tôi đã trích dẫn ở trên nhưng vì ABC là một cơ sở chung, gọi hàm tạo trong mã gây ra một cuộc gọi đệ quy đến '__new__' và treo phiên :-( –

0

Chỉ cần hai xu của tôi ở đây, nhưng tại sao bạn không sử dụng gõ vịt Python để cung cấp Genshi với một cái gì đó hoạt động như một lớp học?

Tôi đã xem nhanh Genshi source code và các yêu cầu duy nhất tôi thấy trên tham số 'lớp' đối với Trình tải mẫu là nó có thể gọi với các đối số đã cho.

Tôi nghĩ sẽ dễ dàng hơn khi giả lập một lớp với chức năng của nhà máy trả về phiên bản được tạo thực tế.

+0

có, sâu trong nguồn nó cuối cùng gọi các nhà xây dựng trên default_class có thể được gọi bất kỳ. Tôi nghĩ đây sẽ là một giải pháp thay thế khả thi. Tương lai có thể khác và nó "có vẻ" đi qua một phân lớp của MarkupTemplate là điều OO đúng để làm. Nhưng một lần nữa nó "dường như" với tôi rằng '__new__' nên có hành vi khác nhau vì vậy tôi là ai để nói ;-) –

Các vấn đề liên quan