อะไรคือความแตกต่างระหว่างสไตล์เก่ากับคลาสสไตล์ใหม่ใน Python


คำตอบ:


559

จากคลาสใหม่และคลาสสิก :

สูงถึง Python 2.1 คลาสแบบเก่าเป็นเพียงรสชาติเดียวที่ผู้ใช้มี

แนวคิดของ (แบบเก่า) ระดับไม่เกี่ยวข้องกับแนวคิดของชนิดถ้าxเป็นตัวอย่างของการเรียนแบบเก่าแล้วx.__class__ กำหนดระดับของxแต่อยู่เสมอtype(x)<type 'instance'>

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

การเรียนแบบใหม่ถูกนำมาใช้ในหลาม 2.2 จะรวมแนวคิดของการเรียนและประเภท คลาสสไตล์ใหม่เป็นเพียงประเภทที่ผู้ใช้กำหนดไม่มากไม่น้อย

ถ้า x เป็นอินสแตนซ์ของคลาสสไตล์ใหม่type(x)โดยทั่วไปจะเป็นเช่นเดียวกันx.__class__(แม้ว่าจะไม่ได้รับการรับรอง - อินสแตนซ์คลาสสไตล์ใหม่จะได้รับอนุญาตให้แทนที่ค่าที่ส่งคืนx.__class__)

แรงจูงใจที่สำคัญสำหรับการแนะนำการเรียนแบบใหม่คือการให้รูปแบบวัตถุแบบครบวงจรกับเมตาเต็มรูปแบบ

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

สำหรับเหตุผลที่เข้ากันได้เรียนยังคงแบบเก่าโดยค่าเริ่มต้น

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

ลักษณะการทำงานของคลาสสไตล์ใหม่แตกต่างจากคลาสแบบเก่าในรายละเอียดที่สำคัญจำนวนหนึ่งนอกเหนือจากประเภทที่ส่งกลับ

การเปลี่ยนแปลงเหล่านี้บางอย่างเป็นพื้นฐานของโมเดลวัตถุใหม่เช่นเดียวกับวิธีการเรียกใช้วิธีพิเศษ อื่น ๆ คือ "การแก้ไข" ที่ไม่สามารถนำมาใช้ก่อนหน้านี้สำหรับความกังวลด้านความเข้ากันได้เช่นลำดับการแก้ไขวิธีการในกรณีที่มีการสืบทอดหลายรายการ

Python 3 มีคลาสสไตล์ใหม่เท่านั้น

ไม่ว่าคุณจะซับคลาสจากobjectหรือไม่ก็ตามคลาสนั้นเป็นสไตล์ใหม่ใน Python 3


41
ความแตกต่างเหล่านี้ไม่เหมือนเหตุผลที่น่าสนใจในการใช้คลาสสไตล์ใหม่ แต่ทุกคนบอกว่าคุณควรใช้สไตล์ใหม่เสมอ type(x)หากฉันใช้เป็ดพิมพ์เหมือนฉันควรฉันไม่จำเป็นต้องใช้ ถ้าฉันไม่ซับคลาสที่มีอยู่แล้วดูเหมือนจะไม่มีประโยชน์ใด ๆ ที่ฉันสามารถเห็นของคลาสสไตล์ใหม่ (object)มีข้อเสียซึ่งเป็นพิมพ์พิเศษของการเป็น
เรียกซ้ำ

78
คุณสมบัติบางอย่างเช่นsuper()ไม่ทำงานในชั้นเรียนแบบเก่า ไม่ต้องพูดถึงอย่างที่บทความกล่าวว่ามีการแก้ไขพื้นฐานเช่น MRO และวิธีการพิเศษซึ่งเป็นมากกว่าเหตุผลที่ดีในการใช้งาน
John Doe

21
@ ผู้ใช้: คลาสแบบเก่ามีพฤติกรรมเหมือนกันใน 2.7 เหมือนที่ทำใน 2.1 - และเนื่องจากมีคนไม่กี่คนที่จำสิ่งแปลกปลอมได้และเอกสารไม่ได้กล่าวถึงพวกเขาส่วนใหญ่อีกต่อไป ใบเสนอราคาเอกสารด้านบนกล่าวถึงสิ่งนี้โดยตรง: มี "การแก้ไข" ที่ไม่สามารถนำไปใช้กับคลาสแบบเก่าได้ ถ้าคุณไม่อยากเจอเรื่องแปลก ๆ ที่ไม่มีใครจัดการตั้งแต่ Python 2.1 และเอกสารอธิบายไม่ได้อธิบายอีกต่อไปอย่าใช้คลาสเก่า ๆ
abarnert

10
นี่คือตัวอย่างของการเล่นโวหารที่คุณอาจสะดุดถ้าคุณใช้คลาสแบบเก่าใน 2.7: bugs.python.org/issue21785
KT

5
สำหรับทุกคนที่สงสัยเหตุผลที่ดีที่จะสืบทอดอย่างชัดเจนจากวัตถุใน Python 3 ก็คือมันทำให้การรองรับ Python หลายรุ่นง่ายขึ้น
jpmc26

308

ประกาศที่ชาญฉลาด:

คลาสสไตล์ใหม่สืบทอดจากวัตถุหรือจากคลาสสไตล์ใหม่อื่น

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

ชั้นเรียนแบบเก่าไม่ได้

class OldStyleClass():
    pass

Python 3 หมายเหตุ:

Python 3 ไม่สนับสนุนคลาสสไตล์เก่าดังนั้นทั้งแบบฟอร์มที่บันทึกไว้ข้างต้นผลลัพธ์ในคลาสสไตล์ใหม่


24
objectถ้าสืบทอดคลาสใหม่สไตล์จากชั้นเรียนแบบใหม่อีกแล้วโดยขยายมันสืบทอดมาจาก
aaronasterling

2
นี่เป็นตัวอย่างที่ไม่ถูกต้องของคลาสไพ ธ อนเก่าหรือไม่? class AnotherOldStyleClass: pass
Ankur Agarwal

11
@abc ฉันเชื่อclass A: passและclass A(): passเทียบเท่าอย่างเคร่งครัด วิธีแรก"ไม่ได้รับมรดกของชนชั้นผู้ปกครอง"และวิธีที่สอง"สืบทอดของระดับผู้ปกครองไม่" ค่อนข้างคล้ายกับnot isและis not
eyquem

5
เช่นเดียวกับหมายเหตุด้านข้างสำหรับ 3.X การสืบทอดของ "object" จะถูกสันนิษฐานโดยอัตโนมัติ (หมายความว่าเราไม่มีทางที่จะไม่สืบทอด "object" ใน 3.X) ด้วยเหตุผลด้านความเข้ากันได้แบบย้อนกลับมันไม่เลวเลยที่จะเก็บ "(วัตถุ)" ไว้ที่นั่น
Yo Hsiao

1
หากเรากำลังจะได้รับเทคนิคเกี่ยวกับคลาสที่สืบทอดมาคำตอบนี้ควรทราบว่าคุณสร้างคลาสคลาสเก่าอีกคลาสโดยสืบทอดจากคลาสสไตล์เก่า (ดังที่เขียนไว้คำตอบนี้จะทำให้ผู้ใช้ตั้งคำถามว่าคุณสามารถรับมรดกจากคลาสแบบเก่าได้หรือไม่)
jpmc26

224

การเปลี่ยนแปลงพฤติกรรมที่สำคัญระหว่างคลาสสไตล์เก่าและใหม่

  • ซุปเปอร์เพิ่ม
  • MRO มีการเปลี่ยนแปลง (อธิบายด้านล่าง)
  • เพิ่มคำอธิบายแล้ว
  • ไม่สามารถยกวัตถุสไตล์คลาสใหม่ได้เว้นแต่จะได้มาจากException(ตัวอย่างด้านล่าง)
  • __slots__ ที่เพิ่ม

MRO (ลำดับการแก้ไขวิธีการ) เปลี่ยนไป

มันถูกกล่าวถึงในคำตอบอื่น ๆ แต่ต่อไปนี้เป็นตัวอย่างที่ชัดเจนของความแตกต่างระหว่าง MRO แบบคลาสสิกและ C3 MRO (ใช้ในคลาสสไตล์ใหม่)

คำถามคือลำดับที่คุณลักษณะ (ซึ่งรวมถึงวิธีการและตัวแปรสมาชิก) ค้นหาในหลายมรดก

คลาสสิกคลาสทำการค้นหาความลึกแรกจากซ้ายไปขวา หยุดในนัดแรก พวกเขาไม่มี__mro__คุณลักษณะ

class C: i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 0
assert C21().i == 2

try:
    C12.__mro__
except AttributeError:
    pass
else:
    assert False

ชั้นเรียนรูปแบบใหม่ MRO มีความซับซ้อนมากขึ้นในการสังเคราะห์ในประโยคภาษาอังกฤษเดียว มันจะมีการอธิบายในรายละเอียดที่นี่ คุณสมบัติอย่างหนึ่งของมันคือคลาสฐานถูกค้นหาเฉพาะเมื่อคลาสที่ได้รับทั้งหมดได้รับแล้ว พวกเขามี__mro__คุณลักษณะที่แสดงลำดับการค้นหา

class C(object): i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 2
assert C21().i == 2

assert C12.__mro__ == (C12, C1, C2, C, object)
assert C21.__mro__ == (C21, C2, C1, C, object)

ไม่สามารถยกวัตถุคลาสสไตล์ใหม่เว้นแต่ได้มาจาก Exception

รอบ ๆ Python 2.5 สามารถเพิ่มได้หลายคลาสและรอบ Python 2.6 จะถูกลบออก บน Python 2.7.3:

# OK, old:
class Old: pass
try:
    raise Old()
except Old:
    pass
else:
    assert False

# TypeError, new not derived from `Exception`.
class New(object): pass
try:
    raise New()
except TypeError:
    pass
else:
    assert False

# OK, derived from `Exception`.
class New(Exception): pass
try:
    raise New()
except New:
    pass
else:
    assert False

# `'str'` is a new style object, so you can't raise it:
try:
    raise 'str'
except TypeError:
    pass
else:
    assert False

8
สรุปชัดเจนดีขอบคุณ เมื่อคุณพูดว่า "ยากที่จะอธิบายเป็นภาษาอังกฤษ" ฉันคิดว่าคุณกำลังอธิบายการค้นหาความลึกครั้งแรกของ postorder เมื่อเทียบกับคลาสแบบเก่าซึ่งใช้การค้นหาความลึกครั้งแรกของการสั่งซื้อล่วงหน้า (preorder หมายถึงเราค้นหาตัวเราก่อนลูกคนแรกของเราและ postorder หมายถึงเราค้นหาตัวเราเองหลังจากลูกคนสุดท้ายของเรา)
Steve Carter

40

คลาสสไตล์เก่ายังคงเร็วกว่าเล็กน้อยสำหรับการค้นหาแอททริบิวต์ โดยปกติจะไม่สำคัญ แต่อาจมีประโยชน์ในโค้ด Python 2.x ที่ไวต่อประสิทธิภาพ:

ใน [3]: คลาส A:
   ... : def __init __ (ตัวเอง):
   ... : self.a = 'สวัสดีนั่น'
   ... :

ใน [4]: ​​class B (วัตถุ):
   ... : def __init __ (ตัวเอง):
   ... : self.a = 'สวัสดีนั่น'
   ... :

ใน [6]: aobj = A ()
ใน [7]: bobj = B ()

ใน [8]:% timeit aobj.a
10,000,000 ลูปที่ดีที่สุดคือ 3: 78.7 ns ต่อวง

ใน [10]:% timeit bobj.a
10,000,000 ลูปส่วนที่ดีที่สุดคือ 3: 86.9 ns ต่อวง

5
น่าสนใจที่คุณสังเกตเห็นในทางปฏิบัติฉันเพิ่งอ่านว่านี่เป็นเพราะคลาสสไตล์ใหม่เมื่อพวกเขาได้พบคุณลักษณะใน dict อินสแตนซ์ต้องทำการค้นหาเพิ่มเติมเพื่อดูว่ามันเป็นคำอธิบายหรือไม่getเมธอดที่ต้องถูกเรียกใช้เพื่อรับค่าที่ส่งคืน คลาสสไตล์เก่าง่าย ๆ ส่งคืนออบเจ็กต์ที่พบโดยไม่มีการคำนวณเพิ่มเติม (แต่ไม่รองรับ descriptors) คุณสามารถอ่านเพิ่มเติมในโพสต์ที่ยอดเยี่ยมนี้โดย Guido python-history.blogspot.co.uk/2010/06/ …โดยเฉพาะหัวข้อในสล็อต
xuloChavez

1
ดูเหมือนจะไม่เป็นจริงกับ CPython 2.7.2:%timeit aobj.a 10000000 loops, best of 3: 66.1 ns per loop %timeit bobj.a 10000000 loops, best of 3: 53.9 ns per loop
Benedikt Waldvogel

1
ยังเร็วกว่าสำหรับ aobj ใน CPython 2.7.2 บน x86-64 Linux สำหรับฉัน
xioxox

41
มันอาจเป็นความคิดที่ดีที่จะพึ่งพารหัส Python บริสุทธิ์สำหรับแอปพลิเคชันที่มีความละเอียดอ่อนด้านประสิทธิภาพ ไม่มีใครพูดว่า: "ฉันต้องการรหัสเร็วดังนั้นฉันจะใช้คลาส Python แบบเก่า" Numpy ไม่นับเป็น Python แท้ๆ
Phillip Cloud

ใน IPython 2.7.6 ก็ไม่เป็นเช่นนั้น '' '' 477 ns เทียบกับ 456 ns ต่อลูป '' ''
kmonsoor

37

Guido ได้เขียนThe Inside Story ในคลาสแบบใหม่ซึ่งเป็นบทความที่ยอดเยี่ยมเกี่ยวกับคลาสใหม่และคลาสเก่าใน Python

Python 3 มีคลาสแบบใหม่เท่านั้น แม้ว่าคุณจะเขียน 'class แบบเก่า' objectก็จะได้มาโดยปริยายจาก

การเรียนแบบใหม่มีคุณลักษณะขั้นสูงบางขาดในชั้นเรียนแบบเก่าเช่นsuperใหม่MRO C3 , บางวิธีการที่มีมนต์ขลัง ฯลฯ


24

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

  1. ชั้นเรียนแบบเก่า

    class Person():
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed1 is ahmed2
    print ahmed1
    print ahmed2
    
    
    >>> False
    <__main__.Person instance at 0xb74acf8c>
    <__main__.Person instance at 0xb74ac6cc>
    >>>
    
  2. คลาสสไตล์ใหม่

    class Person(object):
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed2 is ahmed1
    print ahmed1
    print ahmed2
    
    >>> True
    <__main__.Person object at 0xb74ac66c>
    <__main__.Person object at 0xb74ac66c>
    >>>

2
'_names_cache' ทำอะไร คุณสามารถแบ่งปันข้อมูลอ้างอิงได้หรือไม่
Muatik

4
_names_cacheเป็นพจนานุกรมที่แคช (ร้านค้าสำหรับการเรียกใช้ในอนาคต) Person.__new__ชื่อที่คุณส่งผ่านไปยังทุก เมธอด setdefault (กำหนดในพจนานุกรมใด ๆ ) รับสองอาร์กิวเมนต์: คีย์และค่า หากคีย์อยู่ใน dict มันจะคืนค่าของมัน ถ้ามันไม่อยู่ใน dict มันจะตั้งค่าเป็นค่าที่ส่งผ่านเป็นอาร์กิวเมนต์ที่สองก่อนแล้วจึงส่งคืน
ychaouche

4
การใช้งานไม่ถูกต้อง ความคิดคือการไม่สร้างวัตถุใหม่หากมีอยู่แล้ว แต่ในกรณีของคุณ__new__()จะถูกเรียกเสมอและมันจะสร้างวัตถุใหม่เสมอและจากนั้นจะโยน ในกรณีนี้เป็นที่นิยมมากกว่าif .setdefault()
Amit Upadhyay

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

1
@PraPraPati: มันเป็นการสาธิตราคาถูกที่นี่ __new__ไม่ใช่สิ่งสำหรับคลาสแบบเก่า แต่ไม่ได้ใช้ในการสร้างอินสแตนซ์ (เป็นเพียงชื่อสุ่มที่มีลักษณะพิเศษเช่นการกำหนด__spam__) ดังนั้นการสร้างคลาสแบบเก่าจะเรียกใช้เท่านั้น__init__ในขณะที่การก่อสร้างแบบใหม่จะเรียกใช้__new__(รวมกับอินสแตนซ์เดี่ยวตามชื่อ) เพื่อสร้างและ__init__เริ่มต้นมัน
ShadowRanger

10

คลาสสไตล์ใหม่สืบทอดมาจากobjectและต้องเขียนเช่นนี้ใน Python 2.2 เป็นต้นไป (เช่นclass Classname(object):แทนclass Classname:) การเปลี่ยนแปลงหลักคือการรวมประเภทและคลาสเข้าด้วยกันและผลข้างเคียงที่ดีของสิ่งนี้คือมันช่วยให้คุณสามารถสืบทอดจากประเภทในตัว

อ่านdescrintroสำหรับรายละเอียดเพิ่มเติม


8

คลาสสไตล์ใหม่อาจใช้super(Foo, self)ซึ่งFooเป็นคลาสและselfเป็นอินสแตนซ์

super(type[, object-or-type])

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

และใน Python 3.x คุณสามารถใช้super()ภายในคลาสโดยไม่มีพารามิเตอร์ใด ๆ

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