มีประโยชน์ในการกำหนดคลาสภายในคลาสอื่นใน Python หรือไม่?


107

สิ่งที่ฉันกำลังพูดถึงต่อไปนี้คือคลาสที่ซ้อนกัน โดยพื้นฐานแล้วฉันมีสองคลาสที่ฉันกำลังสร้างโมเดล คลาส DownloadManager และคลาส DownloadThread แนวคิด OOP ที่ชัดเจนในที่นี้คือการจัดองค์ประกอบ อย่างไรก็ตามการจัดองค์ประกอบไม่จำเป็นต้องหมายถึงการซ้อนกันใช่ไหม?

ฉันมีรหัสที่มีลักษณะดังนี้:

class DownloadThread:
    def foo(self):
        pass

class DownloadManager():
    def __init__(self):
        dwld_threads = []
    def create_new_thread():
        dwld_threads.append(DownloadThread())

แต่ตอนนี้ฉันสงสัยว่ามีสถานการณ์ที่การทำรังจะดีขึ้นหรือไม่ สิ่งที่ต้องการ:

class DownloadManager():
    class DownloadThread:
        def foo(self):
            pass
    def __init__(self):
        dwld_threads = []
    def create_new_thread():
        dwld_threads.append(DownloadManager.DownloadThread())

คำตอบ:


123

คุณอาจต้องการทำเช่นนี้เมื่อคลาส "ภายใน" เป็นแบบครั้งเดียวซึ่งจะไม่ถูกใช้นอกคำจำกัดความของคลาสชั้นนอก ตัวอย่างเช่นในการใช้ metaclass บางครั้งก็สะดวกที่จะทำ

class Foo(object):
    class __metaclass__(type):
        .... 

แทนที่จะกำหนด metaclass แยกกันหากคุณใช้เพียงครั้งเดียว

ครั้งเดียวที่ฉันใช้คลาสที่ซ้อนกันแบบนั้นฉันใช้คลาสชั้นนอกเป็นเนมสเปซเพื่อจัดกลุ่มคลาสที่เกี่ยวข้องกันอย่างใกล้ชิด:

class Group(object):
    class cls1(object):
       ...

    class cls2(object):
       ...

จากโมดูลอื่นคุณสามารถนำเข้า Group และอ้างถึงสิ่งเหล่านี้เป็น Group.cls1, Group.cls2 เป็นต้นอย่างไรก็ตามอาจมีคนโต้แย้งว่าคุณสามารถทำสำเร็จได้เหมือนกันทุกประการ (อาจจะสับสนน้อยกว่า) โดยใช้โมดูล


13
ฉัน - ขอเถียงว่าในกรณีที่สองคุณจะได้รับการบริการที่ดีกว่าโดยใช้โมดูลหรือแพ็คเกจซึ่งออกแบบมาให้เป็นเนมสเปซ
Matthew Trevor

5
นี่คือคำตอบที่ยอดเยี่ยม ง่ายตรงประเด็นและกล่าวถึงวิธีการอื่นในการใช้โมดูล +1
CornSmith

5
สำหรับบันทึกสำหรับผู้ที่ดื้อรั้นปฏิเสธที่จะหรือไม่สามารถจัดระเบียบรหัสของตนให้เป็นแพ็กเกจลำดับชั้นและโมดูลที่เหมาะสมการใช้คลาสเป็นเนมสเปซในบางครั้งอาจดีกว่าการไม่ใช้อะไรเลย
Acumenus

20

ฉันไม่รู้จัก Python แต่คำถามของคุณดูเหมือนจะกว้างมาก ไม่ต้องสนใจฉันถ้ามันเฉพาะกับ Python

การซ้อนชั้นเป็นเรื่องของขอบเขต หากคุณคิดว่าคลาสหนึ่งจะเข้าท่าในบริบทของอีกคลาสหนึ่งคลาสก่อนหน้านี้น่าจะเป็นตัวเลือกที่ดีที่จะกลายเป็นคลาสที่ซ้อนกัน

เป็นรูปแบบทั่วไปที่ทำให้คลาสผู้ช่วยเป็นคลาสส่วนตัวที่ซ้อนกัน


9
เพียงเพื่อทราบสำหรับบันทึกแนวคิดของprivateคลาสไม่ได้ใช้มากนักใน Python แต่ขอบเขตการใช้งานโดยนัยยังคงมีผลอย่างแน่นอน
Acumenus

7

มีการใช้งานอื่นสำหรับคลาสที่ซ้อนกันเมื่อต้องการสร้างคลาสที่สืบทอดมาซึ่งฟังก์ชันที่ได้รับการปรับปรุงจะถูกห่อหุ้มไว้ในคลาสที่ซ้อนกันเฉพาะ

ดูตัวอย่างนี้:

class foo:

  class bar:
    ...  # functionalities of a specific sub-feature of foo

  def __init__(self):
    self.a = self.bar()
    ...

  ...  # other features of foo


class foo2(foo):

  class bar(foo.bar):
    ... # enhanced functionalities for this specific feature

  def __init__(self):
    foo.__init__(self)

โปรดสังเกตว่าในตัวสร้างของfooเส้นself.a = self.bar()จะสร้างfoo.barเมื่อวัตถุที่กำลังสร้างเป็นfooวัตถุจริงๆและfoo2.barวัตถุเมื่อวัตถุที่สร้างเป็นfoo2วัตถุจริงๆ

ถ้าชั้นbarถูกกำหนดนอกชั้นเรียนfooแทนเช่นเดียวกับรุ่นที่ได้รับมรดกของตน (ซึ่งจะเรียกว่าbar2ตัวอย่าง) แล้วกำหนดคลาสใหม่foo2จะมากขึ้นเจ็บปวดเพราะ constuctor ของfoo2จะต้องมีบรรทัดแรกของมันถูกแทนที่ด้วยself.a = bar2(), ซึ่งหมายถึงการเขียนตัวสร้างใหม่ทั้งหมด


6

การทำเช่นนี้ไม่มีประโยชน์จริงๆยกเว้นว่าคุณกำลังจัดการกับแว่นตา

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

ชื่อพจนานุกรมและฐานทั้งหมดจะถูกส่งผ่านไปยังฟังก์ชันที่เป็น metaclass จากนั้นจะถูกกำหนดให้กับตัวแปร 'name' ในขอบเขตที่ class: suite อยู่

สิ่งที่คุณจะได้รับจากการยุ่งกับเมตาคลาสสิกและแน่นอนว่าการซ้อนคลาสภายในคลาสสต็อกมาตรฐานของคุณนั้นยากต่อการอ่านโค้ดเข้าใจโค้ดยากขึ้นและข้อผิดพลาดแปลก ๆ ที่ยากต่อการเข้าใจอย่างมากโดยไม่ต้องคุ้นเคยอย่างใกล้ชิดว่าทำไม 'คลาส' ขอบเขตนั้นแตกต่างอย่างสิ้นเชิงกับขอบเขตหลามอื่น ๆ


3
ไม่เห็นด้วย: คลาสข้อยกเว้นการซ้อนมีประโยชน์มากเนื่องจากผู้ใช้ในชั้นเรียนของคุณสามารถตรวจสอบข้อยกเว้นที่คุณกำหนดเองได้โดยไม่ต้องนำเข้าที่วุ่นวาย เช่นexcept myInstanceObj.CustomError, e:
RobM

2
@ เยรู๊บทำไมแย่จัง
Robert Siemer

5

คุณสามารถใช้คลาสเป็นตัวสร้างคลาสได้ ชอบ (ในบางรหัสปิดข้อมือ :)

class gen(object):
    class base_1(object): pass
    ...
    class base_n(object): pass

    def __init__(self, ...):
        ...
    def mk_cls(self, ..., type):
        '''makes a class based on the type passed in, the current state of
           the class, and the other inputs to the method'''

ฉันรู้สึกว่าเมื่อคุณต้องการฟังก์ชันนี้มันจะชัดเจนสำหรับคุณมาก หากคุณไม่จำเป็นต้องทำสิ่งที่คล้ายกันมากกว่านั้นอาจไม่ใช่กรณีการใช้งานที่ดี


1

ไม่องค์ประกอบไม่ได้หมายถึงการทำรัง มันจะสมเหตุสมผลที่จะมีคลาสซ้อนกันถ้าคุณต้องการซ่อนมันเพิ่มเติมในเนมสเปซของคลาสภายนอก

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

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