คลาสภายใน / ซ้อนของ Python ทำให้ฉันสับสน มีบางสิ่งที่ไม่สามารถทำได้หากไม่มีพวกเขา? ถ้าเป็นเช่นนั้นสิ่งนั้นคืออะไร?
คลาสภายใน / ซ้อนของ Python ทำให้ฉันสับสน มีบางสิ่งที่ไม่สามารถทำได้หากไม่มีพวกเขา? ถ้าเป็นเช่นนั้นสิ่งนั้นคืออะไร?
คำตอบ:
อ้างจากhttp://www.geekinterview.com/question_details/64739 :
ข้อดีของชั้นใน:
- การจัดกลุ่มคลาสแบบลอจิคัล : ถ้าคลาสมีประโยชน์กับคลาสอื่นเพียงคลาสเดียวก็มีเหตุผลที่จะฝังคลาสนั้นลงในคลาสนั้น การซ้อน "คลาสตัวช่วย" ดังกล่าวทำให้แพ็กเกจมีความคล่องตัวมากขึ้น
- การห่อหุ้มที่เพิ่มขึ้น : พิจารณาคลาสระดับบนสุดสองคลาส A และ B โดยที่ B ต้องการเข้าถึงสมาชิกของ A ที่จะประกาศให้เป็นส่วนตัว การซ่อนคลาส B ภายในสมาชิกคลาส AA สามารถประกาศเป็นส่วนตัวและ B สามารถเข้าถึงได้ นอกจากนี้ B เองยังสามารถซ่อนตัวจากโลกภายนอกได้
- โค้ดที่อ่านง่ายและบำรุงรักษาได้มากขึ้น : การซ้อนคลาสเล็ก ๆ ภายในคลาสระดับบนสุดจะทำให้โค้ดใกล้กับที่ที่ใช้
ประโยชน์หลักคือการจัดระเบียบ สิ่งใดก็ตามที่สามารถทำได้ด้วยคลาสภายในสามารถทำได้หากไม่มีพวกเขา
DataLoader
คลาสที่สามารถทำให้เกิดCacheMiss
ข้อยกเว้นได้ การซ้อนข้อยกเว้นภายใต้คลาสหลักDataLoader.CacheMiss
หมายความว่าคุณสามารถนำเข้าได้DataLoader
แต่ยังคงใช้ข้อยกเว้น
มีบางสิ่งที่ไม่สามารถทำได้หากไม่มีพวกเขา?
ไม่พวกเขาเทียบเท่ากับการกำหนดชั้นเรียนตามปกติที่ระดับบนสุดแล้วคัดลอกการอ้างอิงไปยังชั้นนอก
ฉันไม่คิดว่ามีเหตุผลพิเศษใด ๆ ที่ชั้นเรียนที่ซ้อนกันได้รับอนุญาตนอกจากนี้ไม่มีเหตุผลใดที่จะ 'ไม่อนุญาต' อย่างชัดเจนเช่นกัน
หากคุณกำลังมองหาคลาสที่มีอยู่ภายในวงจรชีวิตของอ็อบเจ็กต์ภายนอก / เจ้าของและมักจะมีการอ้างอิงถึงอินสแตนซ์ของคลาสภายนอก - คลาสภายในเช่นเดียวกับ Java - คลาสที่ซ้อนกันของ Python จะไม่ใช่สิ่งนั้น แต่คุณสามารถแฮ็กบางอย่างเช่นสิ่งนั้น:
import weakref, new
class innerclass(object):
"""Descriptor for making inner classes.
Adds a property 'owner' to the inner class, pointing to the outer
owner instance.
"""
# Use a weakref dict to memoise previous results so that
# instance.Inner() always returns the same inner classobj.
#
def __init__(self, inner):
self.inner= inner
self.instances= weakref.WeakKeyDictionary()
# Not thread-safe - consider adding a lock.
#
def __get__(self, instance, _):
if instance is None:
return self.inner
if instance not in self.instances:
self.instances[instance]= new.classobj(
self.inner.__name__, (self.inner,), {'owner': instance}
)
return self.instances[instance]
# Using an inner class
#
class Outer(object):
@innerclass
class Inner(object):
def __repr__(self):
return '<%s.%s inner object of %r>' % (
self.owner.__class__.__name__,
self.__class__.__name__,
self.owner
)
>>> o1= Outer()
>>> o2= Outer()
>>> i1= o1.Inner()
>>> i1
<Outer.Inner inner object of <__main__.Outer object at 0x7fb2cd62de90>>
>>> isinstance(i1, Outer.Inner)
True
>>> isinstance(i1, o1.Inner)
True
>>> isinstance(i1, o2.Inner)
False
(สิ่งนี้ใช้ผู้ตกแต่งคลาสซึ่งเป็นของใหม่ใน Python 2.6 และ 3.0 มิฉะนั้นคุณจะต้องพูดว่า“ Inner = innerclass (Inner)” หลังนิยามคลาส)
self
โดยไม่ต้องใช้งานเพิ่มเติมใด ๆ (เพียงใช้ตัวระบุอื่นที่คุณมักจะใส่ด้านในself
เช่นinnerself
) และจะสามารถเข้าถึงอินสแตนซ์ภายนอกผ่านทางนั้นได้
WeakKeyDictionary
ในตัวอย่างนี้ไม่อนุญาตให้รวบรวมคีย์โดยแท้เนื่องจากค่าดังกล่าวอ้างอิงคีย์ตามลำดับอย่างมากผ่านowner
แอตทริบิวต์
มีบางอย่างที่คุณต้องห่อหัวเพื่อให้เข้าใจเรื่องนี้ ในภาษาส่วนใหญ่นิยามคลาสเป็นคำสั่งไปยังคอมไพเลอร์ นั่นคือคลาสจะถูกสร้างขึ้นก่อนที่โปรแกรมจะทำงาน ใน python คำสั่งทั้งหมดสามารถเรียกใช้งานได้ นั่นหมายความว่าคำสั่งนี้:
class foo(object):
pass
เป็นคำสั่งที่ดำเนินการที่รันไทม์เช่นเดียวกับคำสั่งนี้:
x = y + z
ซึ่งหมายความว่าไม่เพียง แต่คุณสามารถสร้างชั้นเรียนภายในชั้นเรียนอื่น ๆ ได้ แต่คุณยังสามารถสร้างชั้นเรียนได้ทุกที่ที่คุณต้องการ พิจารณารหัสนี้:
def foo():
class bar(object):
...
z = bar()
ดังนั้นแนวคิดของ "ชั้นใน" จึงไม่ใช่โครงสร้างภาษา มันเป็นโปรแกรมเมอร์ที่สร้าง กุยโดมีบทสรุปที่ดีมากว่ามันมาที่นี่ได้อย่างไร แต่โดยพื้นฐานแล้วแนวคิดพื้นฐานคือสิ่งนี้ทำให้ไวยากรณ์ของภาษาง่ายขึ้น
การซ้อนชั้นเรียนภายในชั้นเรียน:
คลาสที่ซ้อนกันขยายคำจำกัดความของคลาสทำให้ยากที่จะเห็นว่าเกิดอะไรขึ้น
คลาสที่ซ้อนกันสามารถสร้างการเชื่อมต่อที่จะทำให้การทดสอบยากขึ้น
ใน Python คุณสามารถใส่มากกว่าหนึ่งคลาสในไฟล์ / โมดูลซึ่งแตกต่างจาก Java ดังนั้นคลาสยังคงอยู่ใกล้กับคลาสระดับบนสุดและอาจมีชื่อคลาสที่นำหน้าด้วย "_" เพื่อช่วยบ่งบอกว่าคนอื่นไม่ควรเป็น ใช้มัน
สถานที่ที่คลาสที่ซ้อนกันสามารถพิสูจน์ได้ว่ามีประโยชน์อยู่ในฟังก์ชัน
def some_func(a, b, c):
class SomeClass(a):
def some_method(self):
return b
SomeClass.__doc__ = c
return SomeClass
คลาสรวบรวมค่าจากฟังก์ชันช่วยให้คุณสร้างคลาสแบบไดนามิกเช่นการเขียนโปรแกรมแม่แบบใน C ++
ฉันเข้าใจข้อโต้แย้งของคลาสที่ซ้อนกัน แต่มีบางกรณีที่จะใช้ในบางครั้ง ลองนึกภาพว่าฉันกำลังสร้างคลาสรายการที่เชื่อมโยงแบบทวีคูณและฉันต้องสร้างคลาสโหนดสำหรับการบำรุงรักษาโหนด ฉันมีสองทางเลือกสร้างคลาส Node ภายในคลาส DoublyLinkedList หรือสร้างคลาส Node นอกคลาส DoublyLinkedList ฉันชอบตัวเลือกแรกในกรณีนี้เนื่องจากคลาส Node มีความหมายเฉพาะในคลาส DoublyLinkedList แม้ว่าจะไม่มีประโยชน์ในการซ่อน / ห่อหุ้ม แต่ก็มีประโยชน์ในการจัดกลุ่มที่สามารถบอกว่าคลาส Node เป็นส่วนหนึ่งของคลาส DoublyLinkedList
Node
คลาสเดียวกันไม่มีประโยชน์สำหรับคลาสลิสต์ที่เชื่อมโยงประเภทอื่น ๆ ที่คุณอาจสร้างขึ้นด้วยซึ่งในกรณีนี้มันน่าจะอยู่ภายนอก
Node
อยู่ภายใต้เนมสเปซDoublyLinkedList
และมันก็สมเหตุสมผลที่จะเป็นเช่นนั้น นี่คือ Pythonic: "เนมสเปซเป็นแนวคิดที่ยอดเยี่ยมอย่างหนึ่ง - มาทำสิ่งเหล่านั้นให้มากขึ้น!"
มีบางสิ่งที่ไม่สามารถทำได้หากไม่มีพวกเขา? ถ้าเป็นเช่นนั้นสิ่งนั้นคืออะไร?
มีอะไรบางอย่างที่ไม่สามารถทำได้อย่างง่ายดายโดยไม่ต้อง : มรดกของการเรียนที่เกี่ยวข้อง
นี่คือตัวอย่างมินิมอลที่มีคลาสที่เกี่ยวข้องA
และB
:
class A(object):
class B(object):
def __init__(self, parent):
self.parent = parent
def make_B(self):
return self.B(self)
class AA(A): # Inheritance
class B(A.B): # Inheritance, same class name
pass
รหัสนี้นำไปสู่พฤติกรรมที่สมเหตุสมผลและคาดเดาได้:
>>> type(A().make_B())
<class '__main__.A.B'>
>>> type(A().make_B().parent)
<class '__main__.A'>
>>> type(AA().make_B())
<class '__main__.AA.B'>
>>> type(AA().make_B().parent)
<class '__main__.AA'>
หากB
เป็นคลาสระดับบนสุดคุณไม่สามารถเขียนself.B()
ในเมธอดได้make_B
แต่จะเขียนเพียงอย่างB()
เดียวจึงสูญเสียการเชื่อมโยงแบบไดนามิกกับคลาสที่เพียงพอ
ทราบว่าในการก่อสร้างนี้คุณไม่ควรอ้างถึงชั้นในร่างกายของชั้นเรียนA
B
นี้เป็นแรงจูงใจสำหรับการแนะนำแอตทริบิวต์ในชั้นเรียนparent
B
แน่นอนว่าการเชื่อมโยงแบบไดนามิกนี้สามารถสร้างขึ้นใหม่ได้โดยไม่ต้องใช้คลาสภายในด้วยค่าใช้จ่ายของเครื่องมือที่น่าเบื่อและเกิดข้อผิดพลาดของคลาส
กรณีการใช้งานหลักที่ฉันใช้คือการป้องกันการแพร่กระจายของโมดูลขนาดเล็กและเพื่อป้องกันมลพิษของเนมสเปซเมื่อไม่จำเป็นต้องใช้โมดูลแยกต่างหาก หากฉันกำลังขยายคลาสที่มีอยู่ แต่คลาสที่มีอยู่นั้นจะต้องอ้างอิงคลาสย่อยอื่นที่ควรคู่กับคลาสนั้นเสมอ ตัวอย่างเช่นฉันอาจมีutils.py
โมดูลที่มีคลาสตัวช่วยมากมายซึ่งไม่จำเป็นต้องอยู่คู่กัน แต่ฉันต้องการเสริมสร้างการมีเพศสัมพันธ์สำหรับคลาสตัวช่วยบางคลาส ตัวอย่างเช่นเมื่อฉันใช้https://stackoverflow.com/a/8274307/2718295
: utils.py
:
import json, decimal
class Helper1(object):
pass
class Helper2(object):
pass
# Here is the notorious JSONEncoder extension to serialize Decimals to JSON floats
class DecimalJSONEncoder(json.JSONEncoder):
class _repr_decimal(float): # Because float.__repr__ cannot be monkey patched
def __init__(self, obj):
self._obj = obj
def __repr__(self):
return '{:f}'.format(self._obj)
def default(self, obj): # override JSONEncoder.default
if isinstance(obj, decimal.Decimal):
return self._repr_decimal(obj)
# else
super(self.__class__, self).default(obj)
# could also have inherited from object and used return json.JSONEncoder.default(self, obj)
จากนั้นเราสามารถ:
>>> from utils import DecimalJSONEncoder
>>> import json, decimal
>>> json.dumps({'key1': decimal.Decimal('1.12345678901234'),
... 'key2':'strKey2Value'}, cls=DecimalJSONEncoder)
{"key2": "key2_value", "key_1": 1.12345678901234}
แน่นอนเราสามารถละทิ้งการสืบทอดjson.JSONEnocder
ทั้งหมดและลบล้างค่าเริ่มต้น ():
:
import decimal, json
class Helper1(object):
pass
def json_encoder_decimal(obj):
class _repr_decimal(float):
...
if isinstance(obj, decimal.Decimal):
return _repr_decimal(obj)
return json.JSONEncoder(obj)
>>> json.dumps({'key1': decimal.Decimal('1.12345678901234')}, default=json_decimal_encoder)
'{"key1": 1.12345678901234}'
แต่บางครั้งเพียงเพื่อการประชุมคุณต้องการutils
ประกอบด้วยชั้นเรียนสำหรับการขยาย
นี่คือกรณีการใช้งานอื่น: ฉันต้องการโรงงานสำหรับ mutables ใน OuterClass ของฉันโดยไม่ต้องเรียกใช้copy
:
class OuterClass(object):
class DTemplate(dict):
def __init__(self):
self.update({'key1': [1,2,3],
'key2': {'subkey': [4,5,6]})
def __init__(self):
self.outerclass_dict = {
'outerkey1': self.DTemplate(),
'outerkey2': self.DTemplate()}
obj = OuterClass()
obj.outerclass_dict['outerkey1']['key2']['subkey'].append(4)
assert obj.outerclass_dict['outerkey2']['key2']['subkey'] == [4,5,6]
ฉันชอบรูปแบบนี้มากกว่า@staticmethod
มัณฑนากรที่คุณจะใช้สำหรับฟังก์ชั่นโรงงาน
สองวิธีที่แสดงก่อนหน้านั้นเหมือนกันในการทำงาน อย่างไรก็ตามมีความแตกต่างเล็กน้อยและมีบางสถานการณ์ที่คุณต้องการเลือกอย่างใดอย่างหนึ่ง
วิธีที่ 1: นิยามคลาสที่ซ้อนกัน
(= "คลาสที่ซ้อนกัน")
class MyOuter1:
class Inner:
def show(self, msg):
print(msg)
วิธีที่ 2: ด้วยระดับโมดูลชั้นในที่แนบกับคลาสชั้นนอก
(= "คลาสภายในที่อ้างอิง")
class _InnerClass:
def show(self, msg):
print(msg)
class MyOuter2:
Inner = _InnerClass
ขีดล่างใช้เพื่อติดตามอินเทอร์เฟซภายในของPEP8 "(แพ็กเกจโมดูลคลาสฟังก์ชันคุณลักษณะหรือชื่ออื่น ๆ ) ควรขึ้นต้นด้วยขีดล่างนำหน้าเดียว"
ด้านล่างข้อมูลโค้ดแสดงให้เห็นถึงความคล้ายคลึงกันของฟังก์ชันของ "คลาสที่ซ้อนกัน" กับ "คลาสภายในที่อ้างอิง" พวกเขาจะทำงานในลักษณะเดียวกันในการตรวจสอบโค้ดสำหรับประเภทของอินสแตนซ์คลาสภายใน ไม่จำเป็นต้องพูดว่าพวกm.inner.anymethod()
เขาจะทำงานในทำนองเดียวกันกับm1
และm2
m1 = MyOuter1()
m2 = MyOuter2()
innercls1 = getattr(m1, 'Inner', None)
innercls2 = getattr(m2, 'Inner', None)
isinstance(innercls1(), MyOuter1.Inner)
# True
isinstance(innercls2(), MyOuter2.Inner)
# True
type(innercls1()) == mypackage.outer1.MyOuter1.Inner
# True (when part of mypackage)
type(innercls2()) == mypackage.outer2.MyOuter2.Inner
# True (when part of mypackage)
ความแตกต่างของ "คลาสที่ซ้อนกัน" และ "คลาสภายในที่อ้างอิง" แสดงอยู่ด้านล่าง พวกเขาไม่ใหญ่ แต่บางครั้งคุณอาจต้องการเลือกอย่างใดอย่างหนึ่งตามสิ่งเหล่านี้
ด้วย "คลาสที่ซ้อนกัน" จะสามารถห่อหุ้มโค้ดได้ดีกว่าการใช้ "คลาสภายในที่อ้างอิง" คลาสในเนมสเปซของโมดูลเป็นตัวแปรส่วนกลาง วัตถุประสงค์ของคลาสที่ซ้อนกันคือเพื่อลดความยุ่งเหยิงในโมดูลและทำให้คลาสชั้นในอยู่ในคลาสชั้นนอก
ในขณะที่ไม่มีใครใช้ * from packagename import *
ตัวแปรระดับโมดูลในปริมาณที่ต่ำอาจเป็นสิ่งที่ดีตัวอย่างเช่นเมื่อใช้ IDE ที่มีการเติมโค้ด / intellisense
* ใช่ไหม?
เอกสาร Django แนะนำให้ใช้Meta คลาสภายในสำหรับข้อมูลเมตาของโมเดล มันชัดเจนกว่าเล็กน้อย * ในการสั่งให้ผู้ใช้เฟรมเวิร์กเขียนclass Foo(models.Model)
ด้วย inner class Meta
;
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
แทนที่จะ "เขียน a class _Meta
แล้วเขียนclass Foo(models.Model)
ด้วยMeta = _Meta
";
class _Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
class Ox(models.Model):
Meta = _Meta
horn_length = models.IntegerField()
ด้วยวิธี "คลาสที่ซ้อนกัน" โค้ดสามารถอ่านรายการสัญลักษณ์แสดงหัวข้อย่อยที่ซ้อนกันได้แต่ด้วยเมธอด "คลาสภายในที่อ้างอิง" จะต้องเลื่อนกลับขึ้นไปเพื่อดูคำจำกัดความของ_Meta
การดู "ไอเท็มลูก" (แอตทริบิวต์)
เมธอด "คลาสภายในที่อ้างอิง" สามารถอ่านได้มากขึ้นหากระดับการซ้อนโค้ดของคุณเพิ่มขึ้นหรือแถวยาวด้วยเหตุผลอื่น
* แน่นอนว่าเป็นเรื่องของรสชาติ
นี่ไม่ใช่เรื่องใหญ่ แต่เพื่อความสมบูรณ์: เมื่อเข้าถึงแอตทริบิวต์ที่ไม่มีอยู่จริงสำหรับคลาสชั้นในเราจะเห็นข้อยกเว้นที่แตกต่างกันเล็กน้อย ดำเนินการตามตัวอย่างที่ให้ไว้ในส่วนที่ 2:
innercls1.foo()
# AttributeError: type object 'Inner' has no attribute 'foo'
innercls2.foo()
# AttributeError: type object '_InnerClass' has no attribute 'foo'
นี่เป็นเพราะtype
คลาสภายในคือ
type(innercls1())
#mypackage.outer1.MyOuter1.Inner
type(innercls2())
#mypackage.outer2._InnerClass
ฉันได้ใช้คลาสภายในของ Python เพื่อสร้างคลาสย่อยที่เป็นบั๊กโดยเจตนาภายในฟังก์ชันที่ไม่ได้ใช้งานมากที่สุด (เช่นภายในdef test_something():
) เพื่อให้สามารถครอบคลุมการทดสอบได้มากขึ้น 100% (เช่นการทดสอบคำสั่งการบันทึกที่แทบไม่ได้เรียกใช้โดยการแทนที่วิธีการบางอย่าง)
หากมองย้อนกลับไปจะคล้ายกับคำตอบของ Ed https://stackoverflow.com/a/722036/1101109
ชั้นเรียนภายในดังกล่าวควรอยู่นอกขอบเขตและพร้อมสำหรับการรวบรวมขยะเมื่อการอ้างอิงทั้งหมดถูกลบออกไป ตัวอย่างเช่นใช้inner.py
ไฟล์ต่อไปนี้:
class A(object):
pass
def scope():
class Buggy(A):
"""Do tests or something"""
assert isinstance(Buggy(), A)
ฉันได้รับผลลัพธ์ที่น่าสงสัยต่อไปนี้ภายใต้ OSX Python 2.7.6:
>>> from inner import A, scope
>>> A.__subclasses__()
[]
>>> scope()
>>> A.__subclasses__()
[<class 'inner.Buggy'>]
>>> del A, scope
>>> from inner import A
>>> A.__subclasses__()
[<class 'inner.Buggy'>]
>>> del A
>>> import gc
>>> gc.collect()
0
>>> gc.collect() # Yes I needed to call the gc twice, seems reproducible
3
>>> from inner import A
>>> A.__subclasses__()
[]
คำแนะนำ - อย่าดำเนินการต่อและลองทำสิ่งนี้กับโมเดล Django ซึ่งดูเหมือนจะเก็บการอ้างอิงอื่น ๆ (แคช?) ไว้ในคลาสรถบั๊กกี้ของฉัน
โดยทั่วไปแล้วฉันจะไม่แนะนำให้ใช้คลาสภายในเพื่อวัตถุประสงค์ประเภทนี้เว้นแต่คุณจะให้ความสำคัญกับการทดสอบ 100% และไม่สามารถใช้วิธีอื่นได้ แม้ว่าฉันคิดว่ามันเป็นเรื่องดีที่ได้ทราบว่าหากคุณใช้__subclasses__()
มันบางครั้งอาจทำให้คนชั้นในปนเปื้อนได้ ไม่ว่าจะด้วยวิธีใดถ้าคุณติดตามมาจนถึงตอนนี้ฉันคิดว่าเราค่อนข้างลึกเข้าไปใน Python ณ จุดนี้ดันเดอร์สคอร์ส่วนตัวและทั้งหมด
.__subclasses__()
เพื่อทำความเข้าใจว่าคลาสภายในโต้ตอบกับตัวเก็บขยะอย่างไรเมื่อสิ่งต่างๆอยู่นอกขอบเขตใน Python สิ่งนี้ดูเหมือนจะครอบงำโพสต์ด้วยสายตาดังนั้น 1-3 ย่อหน้าแรกจึงสมควรได้รับการขยายเพิ่มเติมเล็กน้อย