ทำไมไม่เรียกใช้ superclass __init__ โดยอัตโนมัติ


155

เหตุใดนักออกแบบ Python จึงตัดสินใจว่า__init__()วิธีการของคลาสย่อยไม่เรียก__init__()วิธีของซูเปอร์คลาสของพวกเขาโดยอัตโนมัติเช่นเดียวกับในภาษาอื่น ๆ Pythonic และสำนวนที่แนะนำเป็นเช่นนี้หรือไม่

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'

2
คุณสามารถเขียนมัณฑนากรเพื่อสืบทอด__init__เมธอดและอาจค้นหาคลาสย่อยและตกแต่งโดยอัตโนมัติ
osa

2
@osa ฟังความคิดที่ดีจริงๆ คุณต้องการอธิบายเพิ่มเติมเกี่ยวกับส่วนมัณฑนากรอีกเล็กน้อยหรือไม่
Diansheng

1
@osa ใช่อธิบายเพิ่มเติม!
Charlie Parker

คำตอบ:


163

ความแตกต่างที่สำคัญระหว่าง ธ__init__และผู้ที่ภาษาอื่น ๆก่อสร้างคือว่า__init__เป็นไม่ได้คอนสตรัค: มันเป็นค่าเริ่มต้น (ที่เกิดขึ้นจริงคอนสตรัค (ถ้ามี แต่ได้เห็นต่อไปคือ ;-) __new__และการทำงานที่แตกต่างกันอย่างสมบูรณ์อีกครั้ง) ในขณะที่การสร้าง superclasses ทั้งหมด (และไม่มีข้อสงสัยการทำเช่นนั้น "ก่อน" คุณยังคงก่อสร้างลดลง) จะเห็นได้ชัดส่วนหนึ่งของการบอกว่าคุณกำลังสร้างอินสแตนซ์ subclass ของที่ไม่ชัดเจนกรณีสำหรับการเริ่มต้นเนื่องจากมีกรณีการใช้งานหลายอย่างที่การกำหนดค่าเริ่มต้นของซูเปอร์คลาสจำเป็นต้องถูกข้ามแก้ไขควบคุมเกิดขึ้นได้หาก "อยู่ตรงกลาง" ของการกำหนดค่าเริ่มต้นของคลาสย่อยและอื่น ๆ

โดยทั่วไปผู้แทนซุปเปอร์ระดับของการเริ่มต้นที่ไม่ได้เป็นไปโดยอัตโนมัติในหลามสำหรับตรงเหตุผลเดียวกันคณะผู้แทนดังกล่าวยังไม่อัตโนมัติสำหรับการใดวิธีการอื่น ๆ - และทราบว่าผู้ที่ "ภาษาอื่น ๆ" ไม่ได้ทำคณะผู้แทนระดับซุปเปอร์อัตโนมัติสำหรับการใด ๆ วิธีอื่น ๆ เช่น ... เพียงแค่สำหรับนวกรรมิก (และถ้าเป็นไปได้ destructor) ซึ่งตามที่ฉันได้กล่าวไว้ไม่ใช่สิ่งที่ Python __init__เป็น (พฤติกรรมของ__new__ยังค่อนข้างแปลก แต่จริงๆไม่เกี่ยวข้องโดยตรงกับคำถามของคุณเนื่องจาก__new__เป็นตัวสร้างที่แปลกประหลาดที่ไม่จำเป็นต้องสร้างอะไรจริง ๆ - สามารถคืนตัวอย่างที่มีอยู่ได้อย่างสมบูรณ์หรือแม้แต่ไม่ใช่ตัวอย่าง ... ชัดเจน Python ให้คุณมากมายการควบคุมมากขึ้นของกลศาสตร์กว่า "ภาษาอื่น ๆ" คุณมีในใจซึ่งยังรวมถึงการมีคณะผู้แทนไม่มีอัตโนมัติใน__new__ตัวเอง -!)


7
การลงคะแนนเพราะสำหรับฉันประโยชน์ที่แท้จริงคือความสามารถที่คุณพูดถึงเพื่อเรียก _c () ()ของ superclass ณ จุดใด ๆ ในการเริ่มต้นของคลาสย่อย (หรือไม่เลย)
kindall

53
-1 สำหรับ " __init__ไม่ใช่ตัวสร้าง ... ตัวสร้างจริง ... คือ__new__" ในขณะที่คุณสังเกตตัวเอง__new__ทำตัวเหมือนไม่มีตัวสร้างจากภาษาอื่น __init__ในความเป็นจริงคล้ายกันมาก (มันถูกเรียกในระหว่างการสร้างวัตถุใหม่หลังจากที่วัตถุถูกจัดสรรเพื่อตั้งค่าตัวแปรสมาชิกบนวัตถุใหม่) และเกือบจะเป็นสถานที่ที่จะใช้ฟังก์ชั่นการทำงานที่เป็นภาษาอื่น ๆ คอนสตรัค ดังนั้นเพียงแค่เรียกมันเป็นตัวสร้าง!
Ben

8
ฉันยังคิดว่านี่เป็นข้อความที่ค่อนข้างไร้สาระ: "การสร้างซูเปอร์คลาสทั้งหมด ... เป็นส่วนหนึ่งของการพูดอย่างชัดเจนว่าคุณกำลังสร้างอินสแตนซ์ของคลาสย่อยซึ่งไม่ชัดเจนว่าเป็นกรณีของการเริ่มต้น " ไม่มีอะไรในคำสร้าง / การเริ่มต้นที่ทำให้สิ่งนี้ "ชัดเจน" กับทุกคน และ__new__ไม่เรียกใช้ซุปเปอร์คลาสโดยอัตโนมัติ__new__เช่นกัน ดังนั้นการเรียกร้องของคุณว่าความแตกต่างที่สำคัญคือการก่อสร้างจำเป็นต้องมีการสร้างซูเปอร์คลาสในขณะที่การเริ่มต้นไม่สอดคล้องกับการเรียกร้องของคุณที่__new__เป็นผู้สร้าง
Ben

36
ในความเป็นจริงจาก python docs สำหรับ__init__ที่docs.python.org/reference/datamodel.html#basic-customization : "เนื่องจากข้อ จำกัด พิเศษของConstructorไม่มีการคืนค่าใด ๆ การทำเช่นนั้นจะทำให้ TypeError เกิดขึ้นขณะใช้งานจริง "(เน้นที่เหมือง) มันมีอย่างเป็นทางการ__init__เป็นผู้สร้าง
Ben

3
ในคำศัพท์ Python / Java __init__เรียกว่านวกรรมิก คอนสตรัคนี้เป็นฟังก์ชั่นเริ่มต้นที่เรียกว่าหลังจากที่วัตถุได้รับการสร้างอย่างสมบูรณ์และเริ่มต้นเป็นสถานะเริ่มต้นรวมถึงประเภทรันไทม์สุดท้าย มันไม่เทียบเท่ากับ C ++ constructors ที่ถูกเรียกบนวัตถุที่ถูกพิมพ์แบบสแตติกจัดสรรด้วยสถานะที่ไม่ได้กำหนด สิ่งเหล่านี้ก็แตกต่างกัน__new__ดังนั้นเราจึงมีฟังก์ชั่นการจัดสรร / การก่อสร้าง / การเริ่มต้นอย่างน้อยสี่ประเภท ภาษาใช้คำศัพท์แบบผสมและส่วนที่สำคัญคือพฤติกรรมและไม่ใช่คำศัพท์
Elazar

36

ฉันค่อนข้างอายเมื่อมีคนเรียกนกแก้ว "Zen of Python" ราวกับว่ามันเป็นข้ออ้างสำหรับทุกสิ่ง มันเป็นปรัชญาการออกแบบ ตัดสินใจในการออกแบบโดยเฉพาะอย่างยิ่งสามารถเสมอได้รับการอธิบายในแง่เฉพาะเจาะจงมากขึ้น - และพวกเขาจะต้องเป็นหรืออื่น ๆ ว่า "เซนของงูใหญ่" กลายเป็นข้อแก้ตัวสำหรับการทำอะไร

เหตุผลง่าย: คุณไม่จำเป็นต้องสร้างคลาสที่ได้มาในลักษณะที่คล้ายกันกับวิธีที่คุณสร้างคลาสพื้นฐาน คุณอาจมีพารามิเตอร์มากขึ้นน้อยลงพวกเขาอาจอยู่ในลำดับที่แตกต่างกันหรือไม่เกี่ยวข้องเลย

class myFile(object):
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
class readFile(myFile):
    def __init__(self, filename):
        super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
    def __init__(self, mode):
        super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
    def __init__(self, language):
        super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")

__init__นี้นำไปใช้วิธีการที่ได้มาทั้งหมดไม่เพียง


6
ตัวอย่างนี้มีบางสิ่งที่พิเศษหรือไม่? ภาษาแบบคงที่สามารถทำสิ่งนี้ได้เช่นกัน
jean

18

Java และ C ++ ต้องการให้ตัวสร้างคลาสพื้นฐานเรียกใช้เนื่องจากโครงร่างหน่วยความจำ

ถ้าคุณได้เรียนBaseClassกับสมาชิกfield1และคุณสร้างคลาสใหม่SubClassที่เพิ่มเป็นสมาชิกfield2แล้วตัวอย่างของการSubClassมีพื้นที่สำหรับการและfield1 field2คุณจำเป็นต้องมีนวกรรมิกของBaseClassการกรอกข้อมูลfield1เว้นแต่ว่าคุณจะต้องมีการสืบทอดคลาสทั้งหมดเพื่อBaseClassเริ่มต้นซ้ำในตัวสร้างของพวกเขาเอง และถ้าfield1เป็นแบบส่วนตัวการสืบทอดคลาสจะไม่สามารถเริ่มต้นfield1ได้

Python ไม่ใช่ Java หรือ C ++ อินสแตนซ์ทั้งหมดของคลาสที่ผู้ใช้กำหนดทั้งหมดมี 'รูปร่าง' เหมือนกัน โดยทั่วไปแล้วพวกเขาเป็นเพียงพจนานุกรมที่สามารถใส่แอตทริบิวต์ได้ ก่อน initialisation ใด ๆ ที่ได้รับการกระทำทุกกรณีทุกชั้นเรียนที่ผู้ใช้กำหนดเกือบจะเหมือนกัน ; พวกเขาเป็นเพียงที่เก็บคุณสมบัติที่ยังไม่ได้เก็บไว้

ดังนั้นมันจึงเหมาะสมสำหรับคลาสย่อย Python ที่จะไม่เรียกตัวสร้างคลาสพื้นฐาน มันสามารถเพิ่มคุณสมบัติเองได้ถ้าต้องการ ไม่มีพื้นที่สงวนไว้สำหรับจำนวนฟิลด์ที่กำหนดสำหรับแต่ละคลาสในลำดับชั้นและไม่มีความแตกต่างระหว่างแอตทริบิวต์ที่เพิ่มด้วยรหัสจากBaseClassวิธีการและแอตทริบิวต์ที่เพิ่มโดยรหัสจากSubClassวิธีการ

ถ้าตามปกติSubClassแล้วจริง ๆ แล้วต้องการBaseClassตั้งค่า invariants ทั้งหมดก่อนที่จะทำการปรับแต่งของตัวเองแล้วใช่คุณสามารถโทรBaseClass.__init__()(หรือใช้superแต่ที่ซับซ้อนและมีปัญหาของตัวเองบางครั้ง) แต่คุณไม่จำเป็นต้อง และคุณสามารถทำได้ก่อนหรือหลังหรือมีข้อโต้แย้งต่าง ๆ นรกถ้าคุณต้องการคุณสามารถโทรBaseClass.__init__จากวิธีอื่นทั้งหมดกว่า__init__; บางทีคุณอาจมีบางอย่างเริ่มต้นขี้เกียจแปลกประหลาดไป

Python ได้รับความยืดหยุ่นนี้โดยทำให้ทุกอย่างง่ายขึ้น คุณ initialise วัตถุโดยการเขียนวิธีการที่ชุดแอตทริบิวต์__init__ selfแค่นั้นแหละ. มันทำงานเหมือนกับวิธีการเพราะมันเป็นวิธีการที่แน่นอน ไม่มีกฎแปลก ๆ และไม่เข้าใจง่ายอื่น ๆ เกี่ยวกับสิ่งที่ต้องทำก่อนหรือสิ่งที่จะเกิดขึ้นโดยอัตโนมัติหากคุณไม่ทำสิ่งอื่น วัตถุประสงค์เดียวที่จะให้บริการคือการขอให้ดำเนินการในระหว่างการเริ่มต้นวัตถุเพื่อตั้งค่าคุณลักษณะเริ่มต้นและมันจะทำแค่นั้น หากคุณต้องการให้มันทำอย่างอื่นคุณต้องเขียนลงในรหัสของคุณอย่างชัดเจน


2
สำหรับ C ++ ไม่มีส่วนเกี่ยวข้องกับ "layout layout" รูปแบบการเริ่มต้นเช่นเดียวกับข้อเสนอ Python สามารถนำมาใช้ใน C ++ เหตุผลเดียวที่ว่าทำไมการก่อสร้าง / การทำลายเป็นวิธีที่พวกเขาอยู่ใน C ++ เป็นเพราะการตัดสินใจออกแบบเพื่อให้สิ่งอำนวยความสะดวกที่น่าเชื่อถือและมีพฤติกรรมที่ดีสำหรับการจัดการทรัพยากร (RAII) ซึ่งสามารถสร้างขึ้นโดยอัตโนมัติ (หมายถึงรหัสน้อยลง โดยคอมไพเลอร์เนื่องจากกฎสำหรับพวกเขา (คำสั่งเรียก) มีการกำหนดอย่างเข้มงวด ไม่แน่ใจ แต่ Java มีแนวโน้มมากที่สุดเพียงทำตามวิธีการนี้เป็นภาษา C POLA อื่น
Alexander Shukaev

10

"ชัดเจนดีกว่าโดยปริยาย" เป็นเหตุผลเดียวกับที่ระบุว่าเราควรเขียน 'ตัวเอง' อย่างชัดเจน

ฉันคิดว่าในท้ายที่สุดแล้วมันจะมีประโยชน์ - คุณสามารถท่องกฎทั้งหมดที่เกี่ยวกับการเรียกคอนสตรัคเตอร์ของซูเปอร์คลาสได้หรือไม่?


8
ฉันเห็นด้วยกับคุณเป็นส่วนใหญ่ แต่จริงๆแล้วกฎของ Java นั้นค่อนข้างเรียบง่าย: คอนสตรัคเตอร์ no-arg ถูกเรียกใช้เว้นแต่คุณจะถามเฉพาะเจาะจงเป็นพิเศษ
Laurence Gonsalves

1
@ Laurence - เกิดอะไรขึ้นเมื่อคลาส parent ไม่ได้กำหนดคอนสตรัคเตอร์ no-arg? จะเกิดอะไรขึ้นเมื่อคอนสตรัคเตอร์ no-arg ได้รับการปกป้องหรือเป็นส่วนตัว
Mike Axiak

8
เหมือนกับสิ่งเดียวกันที่จะเกิดขึ้นหากคุณพยายามเรียกมันอย่างชัดเจน
Laurence Gonsalves

8

บ่อยครั้งที่คลาสย่อยมีพารามิเตอร์เพิ่มเติมซึ่งไม่สามารถส่งผ่านไปยังซูเปอร์คลาส


7

ตอนนี้เรามีหน้าที่ค่อนข้างยาวที่อธิบายถึงลำดับการแก้ไขเมธอดในกรณีที่มีการสืบทอดหลายรายการ: http://www.python.org/download/releases/2.3/mro/

หากผู้รับเหมาก่อสร้างถูกเรียกโดยอัตโนมัติคุณจะต้องมีอีกหน้าอย่างน้อยความยาวเท่ากันเพื่ออธิบายลำดับของสิ่งที่เกิดขึ้น นั่นจะเป็นนรก ...


นี่คือคำตอบที่ถูกต้อง Python จำเป็นต้องกำหนดซีแมนทิกส์ของการโต้แย้งผ่านระหว่างคลาสย่อยและซูเปอร์คลาส น่าเสียดายที่คำตอบนี้ไม่ได้เพิ่มขึ้น บางทีถ้ามันแสดงปัญหากับตัวอย่าง?
Gary Weiss

5

เพื่อหลีกเลี่ยงความสับสนมีประโยชน์ที่จะทราบว่าคุณสามารถเรียกใช้__init__()เมธอดbase_class ได้หาก child_class ไม่มี__init__()คลาส

ตัวอย่าง:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

ในความเป็นจริงแล้ว MRO ใน python จะค้นหา__init__()ใน parent class เมื่อไม่สามารถหามันได้ในระดับ children คุณต้องเรียกใช้ constructor ของคลาส parent โดยตรงถ้าคุณมี__init__()เมธอดในคลาส children แล้ว

ตัวอย่างเช่นรหัสต่อไปนี้จะส่งคืนข้อผิดพลาด: class parent: def init (self, a = 1, b = 0): self.a = a self.b = b

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.

3

อาจ__init__เป็นวิธีที่ subclass ต้องการแทนที่ บางครั้งคลาสย่อยต้องการฟังก์ชันของพาเรนต์ก่อนจึงจะเพิ่มรหัสเฉพาะคลาสและเวลาอื่น ๆ ที่พวกเขาต้องการตั้งค่าตัวแปรอินสแตนซ์ก่อนที่จะเรียกใช้ฟังก์ชันของพาเรนต์ เนื่องจากไม่มีทางที่ Python จะรู้ได้เมื่อมันเหมาะสมที่สุดในการเรียกใช้ฟังก์ชันเหล่านั้นจึงไม่ควรเดา

หากสิ่งเหล่านั้นไม่ไหวคุณให้พิจารณาว่า__init__เป็นเพียงฟังก์ชั่นอื่น หากฟังก์ชั่นดังกล่าวเป็นปัญหาdostuffแทนคุณจะยังต้องการให้ไพ ธ อนเรียกฟังก์ชันที่เกี่ยวข้องในคลาสพาเรนต์โดยอัตโนมัติหรือไม่?


2

ฉันเชื่อว่าการพิจารณาที่สำคัญอย่างหนึ่งที่นี่คือด้วยการโทรอัตโนมัติsuper.__init__()คุณจะสมัครโดยออกแบบเมื่อวิธีการเริ่มต้นนั้นเรียกว่าและมีข้อโต้แย้งอะไร ละทิ้งการโทรโดยอัตโนมัติและต้องการให้โปรแกรมเมอร์ทำการโทรนั้นอย่างชัดเจนทำให้เกิดความยืดหยุ่นมาก

หลังจากทั้งหมดเพียงเพราะชั้น B มาจากชั้นไม่ได้หมายความว่าสามารถหรือควรจะเรียกว่ามีข้อโต้แย้งเช่นเดียวกับA.__init__() B.__init__()การเรียกอย่างชัดเจนหมายถึงโปรแกรมเมอร์สามารถกำหนดเช่นกำหนดB.__init__()พารามิเตอร์ที่แตกต่างกันโดยสิ้นเชิงทำการคำนวณบางอย่างกับข้อมูลนั้นการเรียกA.__init__()ด้วยอาร์กิวเมนต์ตามความเหมาะสมสำหรับวิธีการนั้นจากนั้นทำการประมวลผลภายหลัง ความยืดหยุ่นเช่นนี้น่าอึดอัดใจที่จะบรรลุว่าA.__init__()จะถูกเรียกB.__init__()โดยปริยายไม่ว่าจะB.__init__()ดำเนินการก่อนหรือหลังก็ตาม

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