super () เพิ่ม“ TypeError: ต้องเป็นประเภทไม่ใช่ classobj” สำหรับคลาสสไตล์ใหม่


335

การใช้งานsuper()TypeError ต่อไปนี้เป็นสาเหตุ:

>>> from  HTMLParser import HTMLParser
>>> class TextParser(HTMLParser):
...     def __init__(self):
...         super(TextParser, self).__init__()
...         self.all_data = []
...         
>>> TextParser()
(...)
TypeError: must be type, not classobj

มีคำถามที่คล้ายกันใน StackOverflow: Python super () เพิ่ม TypeErrorซึ่งข้อผิดพลาดถูกอธิบายโดยข้อเท็จจริงที่ว่าผู้ใช้คลาสนั้นไม่ใช่คลาสสไตล์ใหม่ อย่างไรก็ตามคลาสดังกล่าวเป็นคลาสสไตล์ใหม่เนื่องจากสืบทอดมาจากobject:

>>> isinstance(HTMLParser(), object)
True

ฉันพลาดอะไรไป ฉันจะใช้super()ที่นี่ได้อย่างไร

ใช้HTMLParser.__init__(self)แทนsuper(TextParser, self).__init__()จะได้ผล แต่ฉันอยากจะเข้าใจ TypeError

PS: objectโจอาคิมชี้ให้เห็นว่าการเป็นตัวอย่างใหม่สไตล์ชั้นจะไม่เทียบเท่ากับการเป็น ฉันอ่านตรงกันข้ามหลายครั้งด้วยเหตุนี้ความสับสนของฉัน (ตัวอย่างของการทดสอบคลาสอินสแตนซ์สไตล์ใหม่ตามobjectการทดสอบอินสแตนซ์: https://stackoverflow.com/revisions/2655651/3 )


3
ขอบคุณสำหรับคำถามและคำตอบของคุณ ฉันสงสัยว่าทำไม 2.7 super.__doc__ไม่พูดถึงสไตล์เก่ากับรูปแบบใหม่!
เคลวิน

ขอบคุณ :) เอกสารประกอบมีข้อมูลน้อยกว่าเอกสาร HTML เวอร์ชันเต็ม ความจริงที่ใช้super()งานได้กับคลาสสไตล์ใหม่ (และวัตถุ) นั้นมีการกล่าวถึงในเอกสาร HTML ( docs.python.org/library/functions.html#super )
Eric O Lebigot


นี่ไม่ใช่การทำซ้ำ (ดูคำถามที่ปรับปรุงแล้วและคำตอบที่ยอมรับได้)
Eric O Lebigot

คำตอบ:


246

เอาล่ะมันเป็นเรื่องปกติ " super()ไม่สามารถใช้กับคลาสแบบเก่าได้"

แต่จุดสำคัญคือการทดสอบที่ถูกต้องสำหรับ "นี้ใหม่สไตล์อินสแตนซ์ (เช่นวัตถุ)?" คือ

>>> class OldStyle: pass
>>> instance = OldStyle()
>>> issubclass(instance.__class__, object)
False

และไม่ (ตามคำถาม):

>>> isinstance(instance, object)
True

สำหรับคลาสการทดสอบ "คือคลาสสไตล์ใหม่" ที่ถูกต้องคือ:

>>> issubclass(OldStyle, object)  # OldStyle is not a new-style class
False
>>> issubclass(int, object)  # int is a new-style class
True

จุดสำคัญคือมีการเรียนแบบเก่าที่ระดับของอินสแตนซ์และประเภทมีความแตกต่าง ที่นี่OldStyle().__class__เป็นOldStyleที่ไม่ได้รับมรดกจากobjectในขณะที่type(OldStyle())เป็นinstanceประเภทที่ไม่objectรับมรดกจาก โดยทั่วไปคลาสสไตล์เก่าจะสร้างอ็อบเจกต์ประเภทinstance(ในขณะที่คลาสสไตล์ใหม่จะสร้างออบเจกต์ที่เป็นประเภทคลาสเอง) นี่อาจเป็นสาเหตุที่อินสแตนซ์OldStyle()นั้นเป็นobject: type()สืบทอดมาจากobject(ความจริงที่ว่าคลาสไม่ได้รับสืบทอดobjectไม่นับ: คลาสสไตล์เก่าจะสร้างวัตถุชนิดinstanceใหม่เท่านั้น) การอ้างอิงบางส่วน:https://stackoverflow.com/a/9699961/42973

PS: ความแตกต่างระหว่างคลาสแบบใหม่กับแบบเก่าสามารถเห็นได้ด้วย:

>>> type(OldStyle)  # OldStyle creates objects but is not itself a type
classobj
>>> isinstance(OldStyle, type)
False
>>> type(int)  # A new-style class is a type
type

(คลาสแบบเก่าไม่ใช่ชนิดดังนั้นจึงไม่สามารถเป็นประเภทของอินสแตนซ์ได้)


11
และนี่คือหนึ่งในเหตุผลที่เรามี Python 3 อยู่แล้ว
Steven Rumbalski

2
BTW: (Oldstyle().__class__ is Oldstyle)isTrue
Tino

2
@Tino: แน่นอน แต่ประเด็นOldStyle().__class__คือเพื่อแสดงวิธีการทดสอบว่าวัตถุ ( OldStyle()) มาจากคลาสแบบเก่าหรือไม่ ด้วยคลาสแบบใหม่ที่มีอยู่ในใจเท่านั้นจึงอาจถูกล่อลวงให้ทำแบบทดสอบisinstance(OldStyle(), object)แทน
Eric O Lebigot

27
มันเป็นความผิดปกติเท่าใดของห้องสมุดมาตรฐานหลามยังคงอยู่ใน 2.7.x ไม่ได้รับมรดกจากobjectจึงคาดคั้นคุณโดยการมอบฉันทะ
Nick Bastin

2
@NickBastin - นี่ไม่ใช่เรื่องบังเอิญ มันคือทั้งหมดที่จะผลักดันให้ทุกคนเข้าสู่ Python 3 ที่ "ทุกอย่างเรียบร้อยดีอยู่แล้ว" แต่ - ข้อแม้ emptor - เป็นเพียงเหยื่อและเปลี่ยน
Tomasz Gandor

204

super () สามารถใช้ได้เฉพาะในคลาสสไตล์ใหม่เท่านั้นซึ่งหมายความว่าคลาสรูทจำเป็นต้องสืบทอดจากคลาส 'object'

ตัวอย่างเช่นชั้นบนสุดจะต้องเป็นเช่นนี้:

class SomeClass(object):
    def __init__(self):
        ....

ไม่

class SomeClass():
    def __init__(self):
        ....

ดังนั้นการแก้ปัญหาคือการที่เรียกวิธีการเริ่มต้นของผู้ปกครองโดยตรงเช่นนี้

class TextParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.all_data = []

8
สำหรับฉันฉันต้องทำสิ่งนี้: HTMLParser .__ init __ (ตัวเอง) ฉันอยากรู้ว่าตัวอย่างสุดท้ายของคุณทำงานอย่างไร
chaimp

1
@EOL หมายความว่าอย่างไร เจฟฟ์เพิ่งชี้ให้เห็นว่ารหัสที่ให้ไว้ในคำตอบนี้ผิดเนื่องจากขาดselfพารามิเตอร์ในการHTMLParser.__init__()โทร
Piotr Dobrogost

1
@PiotrDobrogost: ขออภัยคำพูดของฉันเกี่ยวกับคำตอบของ LittleQ ไม่ใช่เกี่ยวกับจุด (ดี) ของ jeffp
Eric O Lebigot

1
@jeffp ขออภัยมันเป็นคำผิดฉันพิมพ์มันลงไปใน SO แต่ไม่ได้ทดสอบความผิดของฉัน ขอบคุณสำหรับการแก้ไข
โคลินซู

1
โหวตขึ้นสำหรับการแก้ไขที่ทำงานร่วมกับรหัสที่มีอยู่เช่นการบันทึกรูปแบบใน python2.6
David Reynolds

28

class TextParser(HTMLParser, object):นอกจากนี้คุณยังสามารถใช้ นี้จะทำให้ใหม่สไตล์ชั้นเรียนและสามารถนำมาใช้TextParsersuper()


การอัปโหลดจากฉันเนื่องจากการเพิ่มการสืบทอดจากวัตถุเป็นความคิดที่ดี (ที่กล่าวว่าคำตอบนี้ไม่สามารถแก้ไขปัญหาของการทำความเข้าใจ TypeError ของคำถามที่.)
เอริคโอ Lebigot

23

ปัญหาคือsuperต้องobjectเป็นบรรพบุรุษ:

>>> class oldstyle:
...     def __init__(self): self.os = True

>>> class myclass(oldstyle):
...     def __init__(self): super(myclass, self).__init__()

>>> myclass()
TypeError: must be type, not classobj

จากการตรวจสอบอย่างใกล้ชิดพบว่า:

>>> type(myclass)
classobj

แต่:

>>> class newstyle(object): pass

>>> type(newstyle)
type    

ดังนั้นวิธีแก้ปัญหาของคุณคือการสืบทอดจากวัตถุเช่นเดียวกับจาก HTMLParser แต่ตรวจสอบให้แน่ใจวัตถุมาล่าสุดในชั้นเรียน MRO:

>>> class myclass(oldstyle, object):
...     def __init__(self): super(myclass, self).__init__()

>>> myclass().os
True

คะแนนที่ถูกต้อง แต่เป็นคำตอบก่อนหน้านี้แล้ว นอกจากนี้แทนที่จะตรวจสอบtype(myclass)สิ่งที่สำคัญคือว่าmyclassสืบทอดมาจากวัตถุ (เช่นไม่ว่าจะisinstance(myclass, object)เป็นจริง - มันเป็นเท็จ)
Eric O Lebigot

17

ถ้าคุณมองไปที่ต้นไม้มรดก (ในรุ่น 2.6) HTMLParserที่สืบทอดจากSGMLParserซึ่งสืบทอดมาจากParserBaseที่ไม่ได้objectสืบทอดมาจาก Ie HTMLParser เป็นคลาสแบบเก่า

เกี่ยวกับการตรวจสอบของคุณisinstanceฉันได้ทำการทดสอบอย่างรวดเร็วใน ipython

ใน [1]: คลาส A:
   ... : ผ่าน
   ... : 

ใน [2]: isinstance (A, object)
ออก [2]: จริง

objectแม้ว่าชั้นเรียนเป็นชั้นแบบเก่าก็ยังคงเป็นตัวอย่างของ


2
ฉันเชื่อว่าการทดสอบที่ถูกต้องควรisinstance(A(), object)ไม่ใช่isinstance(A, object)ไม่ใช่? ด้วยหลังคุณกำลังทดสอบว่าชั้น Aเป็นobjectในขณะที่คำถามคือว่ากรณีของการAเป็นobjectใช่มั้ย?
Eric O Lebigot

5
PS: การทดสอบที่ดีที่สุดน่าจะเป็นissubclass(HTMLParser, object)ซึ่งจะส่งกลับเท็จ
Eric O Lebigot

5

วิธีที่ถูกต้องที่จะทำจะเป็นดังต่อไปนี้ในชั้นเรียนแบบเก่าที่ไม่สืบทอดมาจาก 'object'

class A:
    def foo(self):
        return "Hi there"

class B(A):
    def foo(self, name):
        return A.foo(self) + name

1
ในคำถามวิธีการนี้ถูกกล่าวถึงแล้ว: ปัญหาคือการเข้าใจว่าเหตุใดจึงเกิดข้อความแสดงข้อผิดพลาดขึ้นเพื่อไม่ให้หายไป (โดยเฉพาะอย่างยิ่งในลักษณะนี้)
Eric O Lebigot

นอกจากนี้วิธีนี้จะไม่ทำงานในกรณีที่เราต้องการเรียกอินสแตนซ์ของคลาสAที่เก็บสถานะ
tashuhka

0

FWIW และถึงแม้ว่าฉันไม่ใช่กูรู Python ที่ฉันได้รับจากสิ่งนี้

>>> class TextParser(HTMLParser):
...    def handle_starttag(self, tag, attrs):
...        if tag == "b":
...            self.all_data.append("bold")
...        else:
...            self.all_data.append("other")
...     
...         
>>> p = TextParser()
>>> p.all_data = []
>>> p.feed(text)
>>> print p.all_data
(...)

เพิ่งได้ผลลัพธ์การแยกวิเคราะห์กลับตามที่ต้องการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.