อะไรคือความแตกต่างระหว่างสไตล์เก่ากับคลาสสไตล์ใหม่ใน Python เมื่อใดที่ฉันควรใช้รายการใดรายการหนึ่ง
อะไรคือความแตกต่างระหว่างสไตล์เก่ากับคลาสสไตล์ใหม่ใน Python เมื่อใดที่ฉันควรใช้รายการใดรายการหนึ่ง
คำตอบ:
จากคลาสใหม่และคลาสสิก :
สูงถึง 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
super()
ไม่ทำงานในชั้นเรียนแบบเก่า ไม่ต้องพูดถึงอย่างที่บทความกล่าวว่ามีการแก้ไขพื้นฐานเช่น MRO และวิธีการพิเศษซึ่งเป็นมากกว่าเหตุผลที่ดีในการใช้งาน
ประกาศที่ชาญฉลาด:
คลาสสไตล์ใหม่สืบทอดจากวัตถุหรือจากคลาสสไตล์ใหม่อื่น
class NewStyleClass(object):
pass
class AnotherNewStyleClass(NewStyleClass):
pass
ชั้นเรียนแบบเก่าไม่ได้
class OldStyleClass():
pass
Python 3 หมายเหตุ:
Python 3 ไม่สนับสนุนคลาสสไตล์เก่าดังนั้นทั้งแบบฟอร์มที่บันทึกไว้ข้างต้นผลลัพธ์ในคลาสสไตล์ใหม่
object
ถ้าสืบทอดคลาสใหม่สไตล์จากชั้นเรียนแบบใหม่อีกแล้วโดยขยายมันสืบทอดมาจาก
class AnotherOldStyleClass: pass
class A: pass
และclass A(): pass
เทียบเท่าอย่างเคร่งครัด วิธีแรก"ไม่ได้รับมรดกของชนชั้นผู้ปกครอง"และวิธีที่สอง"สืบทอดของระดับผู้ปกครองไม่" ค่อนข้างคล้ายกับnot is
และis not
การเปลี่ยนแปลงพฤติกรรมที่สำคัญระหว่างคลาสสไตล์เก่าและใหม่
Exception
(ตัวอย่างด้านล่าง)__slots__
ที่เพิ่มมันถูกกล่าวถึงในคำตอบอื่น ๆ แต่ต่อไปนี้เป็นตัวอย่างที่ชัดเจนของความแตกต่างระหว่าง 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
คลาสสไตล์เก่ายังคงเร็วกว่าเล็กน้อยสำหรับการค้นหาแอททริบิวต์ โดยปกติจะไม่สำคัญ แต่อาจมีประโยชน์ในโค้ด 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 ต่อวง
%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
Guido ได้เขียนThe Inside Story ในคลาสแบบใหม่ซึ่งเป็นบทความที่ยอดเยี่ยมเกี่ยวกับคลาสใหม่และคลาสเก่าใน Python
Python 3 มีคลาสแบบใหม่เท่านั้น แม้ว่าคุณจะเขียน 'class แบบเก่า' object
ก็จะได้มาโดยปริยายจาก
การเรียนแบบใหม่มีคุณลักษณะขั้นสูงบางขาดในชั้นเรียนแบบเก่าเช่นsuper
ใหม่MRO C3 , บางวิธีการที่มีมนต์ขลัง ฯลฯ
นี่คือความแตกต่างที่เป็นจริงมากจริง / เท็จ ข้อแตกต่างระหว่างทั้งสองรุ่นของรหัสต่อไปนี้ที่อยู่ในรุ่นที่สองคนสืบทอดจากวัตถุ นอกเหนือจากนั้นทั้งสองรุ่นเหมือนกัน แต่มีผลลัพธ์ที่แตกต่าง:
ชั้นเรียนแบบเก่า
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>
>>>
คลาสสไตล์ใหม่
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>
>>>
_names_cache
เป็นพจนานุกรมที่แคช (ร้านค้าสำหรับการเรียกใช้ในอนาคต) Person.__new__
ชื่อที่คุณส่งผ่านไปยังทุก เมธอด setdefault (กำหนดในพจนานุกรมใด ๆ ) รับสองอาร์กิวเมนต์: คีย์และค่า หากคีย์อยู่ใน dict มันจะคืนค่าของมัน ถ้ามันไม่อยู่ใน dict มันจะตั้งค่าเป็นค่าที่ส่งผ่านเป็นอาร์กิวเมนต์ที่สองก่อนแล้วจึงส่งคืน
__new__()
จะถูกเรียกเสมอและมันจะสร้างวัตถุใหม่เสมอและจากนั้นจะโยน ในกรณีนี้เป็นที่นิยมมากกว่าif
.setdefault()
__new__
ไม่ใช่สิ่งสำหรับคลาสแบบเก่า แต่ไม่ได้ใช้ในการสร้างอินสแตนซ์ (เป็นเพียงชื่อสุ่มที่มีลักษณะพิเศษเช่นการกำหนด__spam__
) ดังนั้นการสร้างคลาสแบบเก่าจะเรียกใช้เท่านั้น__init__
ในขณะที่การก่อสร้างแบบใหม่จะเรียกใช้__new__
(รวมกับอินสแตนซ์เดี่ยวตามชื่อ) เพื่อสร้างและ__init__
เริ่มต้นมัน
คลาสสไตล์ใหม่สืบทอดมาจากobject
และต้องเขียนเช่นนี้ใน Python 2.2 เป็นต้นไป (เช่นclass Classname(object):
แทนclass Classname:
) การเปลี่ยนแปลงหลักคือการรวมประเภทและคลาสเข้าด้วยกันและผลข้างเคียงที่ดีของสิ่งนี้คือมันช่วยให้คุณสามารถสืบทอดจากประเภทในตัว
อ่านdescrintroสำหรับรายละเอียดเพิ่มเติม
คลาสสไตล์ใหม่อาจใช้super(Foo, self)
ซึ่งFoo
เป็นคลาสและself
เป็นอินสแตนซ์
super(type[, object-or-type])
ส่งคืนวัตถุพร็อกซี่ที่มอบหมายวิธีการโทรไปยังประเภทผู้ปกครองหรือพี่น้องประเภท สิ่งนี้มีประโยชน์สำหรับการเข้าถึงวิธีการสืบทอดที่ถูกแทนที่ในชั้นเรียน ลำดับการค้นหาเหมือนกับที่ใช้โดย getattr () ยกเว้นว่าตัวประเภทนั้นถูกข้ามไป
และใน Python 3.x คุณสามารถใช้super()
ภายในคลาสโดยไม่มีพารามิเตอร์ใด ๆ
type(x)
หากฉันใช้เป็ดพิมพ์เหมือนฉันควรฉันไม่จำเป็นต้องใช้ ถ้าฉันไม่ซับคลาสที่มีอยู่แล้วดูเหมือนจะไม่มีประโยชน์ใด ๆ ที่ฉันสามารถเห็นของคลาสสไตล์ใหม่(object)
มีข้อเสียซึ่งเป็นพิมพ์พิเศษของการเป็น