ไม่สามารถตั้งค่าแอตทริบิวต์ในอินสแตนซ์ของคลาส "object"


88

ดังนั้นฉันกำลังเล่นกับ Python ในขณะที่ตอบคำถามนี้และฉันพบว่ามันไม่ถูกต้อง:

o = object()
o.attr = 'hello'

เนื่องจากAttributeError: 'object' object has no attribute 'attr'ไฟล์. อย่างไรก็ตามด้วยคลาสใด ๆ ที่สืบทอดมาจากอ็อบเจ็กต์มันถูกต้อง:

class Sub(object):
    pass

s = Sub()
s.attr = 'hello'

การพิมพ์s.attrแสดง 'สวัสดี' ตามที่คาดไว้ เหตุใดจึงเป็นเช่นนี้ สิ่งใดในข้อกำหนดภาษา Python ระบุว่าคุณไม่สามารถกำหนดแอตทริบิวต์ให้กับออบเจ็กต์วานิลลาได้


การคาดเดาอย่างแท้จริง: objectประเภทไม่เปลี่ยนรูปและไม่สามารถเพิ่มคุณสมบัติใหม่ได้? ดูเหมือนว่ามันจะเข้าท่าที่สุด
Chris Lutz

2
@ S.Lott: ดูบรรทัดแรกของคำถามนี้ ความอยากรู้อยากเห็นล้วนๆ
Smashery

3
ชื่อของคุณทำให้เข้าใจผิดคุณกำลังพยายามตั้งค่าแอตทริบิวต์ในobjectอินสแตนซ์ของคลาสไม่ใช่ในobjectคลาส
dhill

คำตอบ:


130

ในการรองรับการกำหนดแอตทริบิวต์โดยพลการอ็อบเจกต์ต้องการ a __dict__: dict ที่เชื่อมโยงกับอ็อบเจ็กต์ซึ่งสามารถจัดเก็บแอ็ตทริบิวต์ตามอำเภอใจได้ มิฉะนั้นจะไม่มีที่ให้ใส่แอตทริบิวต์ใหม่ได้

ตัวอย่างของการobjectไม่ได้ดำเนินการรอบ__dict__- ถ้ามันไม่ได้ก่อนที่ปัญหาการพึ่งพาอาศัยกันที่น่ากลัววงกลม (ตั้งแต่dictเช่นทุกอย่างอื่นมากที่สุด, สืบทอดจากobject;-) นี้จะอานทุกวัตถุในหลามกับกิงดิคซึ่งจะหมายถึงค่าใช้จ่าย ของหลายไบต์ต่อวัตถุว่าขณะนี้ไม่ได้หรือต้อง Dict (ส่วนวัตถุทั้งหมดที่ไม่ได้มีแอตทริบิวต์มอบหมายโดยพลการไม่ได้หรือต้องการ Dict ก)

ตัวอย่างเช่นการใช้pymplerโครงการที่ยอดเยี่ยม(คุณสามารถรับผ่าน svn ได้จากที่นี่ ) เราสามารถทำการวัดได้ ... :

>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16

คุณไม่ต้องการให้ทุกคนintใช้ 144 ไบต์แทนที่จะเป็น 16 ใช่ไหม -)

ตอนนี้เมื่อคุณสร้างคลาส (สืบทอดจากอะไรก็ตาม) สิ่งต่างๆก็เปลี่ยนไป ... :

>>> class dint(int): pass
... 
>>> asizeof.asizeof(dint(23))
184

ที่ ... __dict__ ถูกเพิ่มในขณะนี้ (บวกเล็ก ๆ น้อย ๆ ค่าใช้จ่าย) - ดังนั้นdintตัวอย่างเช่นสามารถมีแอตทริบิวต์โดยพลการ แต่คุณต้องจ่ายค่าใช้จ่ายค่อนข้างพื้นที่สำหรับความยืดหยุ่นที่

แล้วถ้าคุณต้องการints ที่มีคุณสมบัติพิเศษเพียงอย่างเดียวfoobar ... ? มันเป็นความต้องการที่หายาก แต่ Python มีกลไกพิเศษสำหรับวัตถุประสงค์ ...

>>> class fint(int):
...   __slots__ = 'foobar',
...   def __init__(self, x): self.foobar=x+100
... 
>>> asizeof.asizeof(fint(23))
80

... ไม่ได้ค่อนข้างเล็ก ๆ เป็นintใจคุณ! (หรือแม้กระทั่งสองints หนึ่งselfและหนึ่งself.foobar- อันที่สองสามารถกำหนดใหม่ได้) แต่ดีกว่า a dint.

เมื่อคลาสมี__slots__แอตทริบิวต์พิเศษ (ลำดับของสตริง) ดังนั้นclassคำสั่ง (แม่นยำยิ่งขึ้นเมตาคลาสเริ่มต้นtype) จะไม่จัดให้ทุกอินสแตนซ์ของคลาสนั้นมี a __dict__(ดังนั้นความสามารถในการมีแอตทริบิวต์โดยพลการ) เพียง จำกัด ชุด "ช่อง" แบบแข็ง (โดยทั่วไปจะเป็นตำแหน่งที่แต่ละชิ้นสามารถอ้างอิงถึงวัตถุบางอย่างได้) พร้อมชื่อที่กำหนด

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


5
สิ่งนี้อธิบายถึงวิธีการใช้งานกลไก แต่ไม่ได้อธิบายว่าเหตุใดจึงนำมาใช้ในลักษณะนี้ ฉันจะคิดว่าอย่างน้อยสองหรือสามวิธีในการดำเนินการเพิ่มDictได้ทันทีซึ่งจะไม่ได้มีข้อเสียค่าใช้จ่าย แต่จะเพิ่มความเรียบง่ายบาง
ГеоргиКременлиев

โปรดทราบว่าไม่ว่างเปล่า__slots__จะไม่ได้ทำงานกับชนิดความยาวตัวแปรเช่น str, tupleและในหลาม int3
arekolek


มันเป็นคำอธิบายที่ดี แต่ก็ยังไม่ได้คำตอบว่าทำไม (หรืออย่างไร) ถึงSubมี__dict__แอตทริบิวต์และวัตถุไม่ได้Subรับการสืบทอดมาจากobjectนั้นแอตทริบิวต์นั้น (และอื่น ๆ เช่น__module__) เพิ่มในการสืบทอดอย่างไร อาจเป็นคำถามใหม่
Rodrigo E. Principe

3
ออบเจ็กต์__dict__จะถูกสร้างขึ้นในครั้งแรกที่จำเป็นเท่านั้นดังนั้นสถานการณ์ต้นทุนหน่วยความจำจึงไม่ง่ายอย่างที่asizeofผลลัพธ์ออกมา ( asizeofไม่ทราบวิธีการหลีกเลี่ยง__dict__การเป็นตัวเป็นตน.) คุณสามารถดู Dict ไม่ได้รับรูปธรรมจนต้องในตัวอย่างนี้และคุณสามารถเห็นหนึ่งของเส้นทางรหัสที่รับผิดชอบในการ__dict__เป็นตัวเป็นตนที่นี่
user2357112 รองรับ Monica

17

ดังที่ผู้ตอบรายอื่นกล่าวไว้ว่า an objectไม่มี__dict__. objectเป็นชั้นฐานของทุกประเภทรวมถึงหรือint strดังนั้นสิ่งที่จัดหาให้objectก็จะเป็นภาระแก่พวกเขาเช่นกัน แม้แต่สิ่งที่เรียบง่ายอย่างตัวเลือก __dict__ก็จำเป็นต้องมีตัวชี้เพิ่มเติมสำหรับแต่ละค่า สิ่งนี้จะเสียหน่วยความจำเพิ่มเติม 4-8 ไบต์สำหรับแต่ละอ็อบเจ็กต์ในระบบสำหรับยูทิลิตี้ที่ จำกัด มาก


แทนที่จะทำอินสแตนซ์ของคลาสดัมมี่ใน Python 3.3+ คุณสามารถ (และควร) ใช้types.SimpleNamespaceสำหรับสิ่งนี้


4

เป็นเพียงเพราะการเพิ่มประสิทธิภาพ

Dicts มีขนาดค่อนข้างใหญ่

>>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140

คลาสส่วนใหญ่ (อาจทั้งหมด) ที่กำหนดไว้ใน C ไม่มีคำสั่งสำหรับการปรับให้เหมาะสม

หากคุณดูที่ซอร์สโค้ดคุณจะเห็นว่ามีการตรวจสอบมากมายเพื่อดูว่าวัตถุนั้นมีคำสั่งหรือไม่


1

ดังนั้นจากการตรวจสอบคำถามของตัวเองฉันค้นพบสิ่งนี้เกี่ยวกับภาษา Python: คุณสามารถสืบทอดจากสิ่งต่างๆเช่น int และคุณจะเห็นพฤติกรรมเดียวกัน:

>>> class MyInt(int):
       pass

>>> x = MyInt()
>>> print x
0
>>> x.hello = 4
>>> print x.hello
4
>>> x = x + 1
>>> print x
1
>>> print x.hello
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: 'int' object has no attribute 'hello'

ฉันถือว่าข้อผิดพลาดในตอนท้ายเป็นเพราะฟังก์ชัน add ส่งคืน int ดังนั้นฉันจึงต้องแทนที่ฟังก์ชันเช่น__add__นี้เพื่อรักษาแอตทริบิวต์ที่กำหนดเองของฉัน แต่ตอนนี้มันสมเหตุสมผลแล้วสำหรับฉัน (ฉันคิดว่า) เมื่อฉันคิดถึง "object" เช่น "int"


0

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


แต่วัตถุ () เป็นวัตถุเช่นเดียวกับ Sub () คือวัตถุ ความเข้าใจของฉันคือทั้ง s และ o เป็นวัตถุ แล้วความแตกต่างพื้นฐานระหว่าง s กับ o คืออะไร? มันเป็นประเภทที่สร้างอินสแตนซ์และอีกประเภทหนึ่งเป็นคลาสที่สร้างอินสแตนซ์หรือไม่?
Smashery

บิงโก ตรงประเด็นเลย
Ryan

1
ใน Python 3 ความแตกต่างระหว่างประเภทและคลาสไม่มีอยู่จริง ดังนั้น "type" & "class" จึงค่อนข้างตรงกัน แต่คุณยังไม่สามารถเพิ่มแอตทริบิวต์ให้กับคลาสที่ไม่มี a ได้__dict__ด้วยเหตุผลที่ Alex Martelli ให้ไว้
PM 2Ring


-2

นี่คือ (IMO) หนึ่งในข้อ จำกัด พื้นฐานของ Python - คุณไม่สามารถเปิดคลาสใหม่ได้ ฉันเชื่อว่าปัญหาที่แท้จริงเกิดจากการที่คลาสที่ใช้ใน C ไม่สามารถแก้ไขได้ที่รันไทม์ ... คลาสย่อยสามารถทำได้ แต่ไม่ใช่คลาสพื้นฐาน

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