การเข้าถึงที่อยู่หน่วยความจำวัตถุ


168

เมื่อคุณเรียกใช้object.__repr__()เมธอดใน Python คุณจะได้สิ่งนี้กลับมา

<__main__.Test object at 0x2aba1c0cf890> 

มีวิธีใดบ้างที่จะเก็บที่อยู่หน่วยความจำไว้หากคุณใช้งานมากเกินไป__repr__()โทรไปยังsuper(Class, obj).__repr__()หมายเลขอื่นแล้วโทรออกหรือไม่

คำตอบ:


208

คู่มืองูใหญ่นี้จะพูดเกี่ยวกับการมีid():

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

ดังนั้นใน CPython นี่จะเป็นที่อยู่ของวัตถุ ไม่มีการรับประกันดังกล่าวสำหรับล่าม Python อื่น ๆ

โปรดทราบว่าหากคุณเขียนส่วนขยาย C คุณสามารถเข้าถึง internals ของ Python interpreter รวมถึงการเข้าถึงที่อยู่ของวัตถุโดยตรง


7
นี่ไม่ใช่คำตอบสำหรับคำถามสากล ใช้กับ CPython เท่านั้น
DilithiumMatrix

5
หมายเหตุถึงตัวเอง: การรับประกันใช้ไม่ได้กับการประมวลผลหลายตัว
รูฟัส

1
วิธีการใช้งานบางอย่าง (เพื่อเปรียบเทียบค่าที่มีอยู่): forum.freecodecamp.com/t/python-id-object/19207
J. Does

วัตถุlifetime(และสิ่งที่มีความหมายตลอดชีวิตoverlap/not overlap) หมายถึงอะไรในบริบทนี้?
Minh Tran

4
@MinhTran เพราะ id เป็นที่อยู่หน่วยความจำของวัตถุมันรับประกันว่าไม่ซ้ำกันภายในกระบวนการและในขณะที่วัตถุที่มี บางครั้งหลังจากวัตถุถูกรวบรวมขยะหน่วยความจำอาจถูกนำมาใช้ใหม่ อายุการใช้งานที่ไม่ทับซ้อนกันหมายถึงวัตถุต้นฉบับไม่มีอยู่อีกต่อไปเมื่อมีการสร้างวัตถุใหม่ ดังนั้นข้อ จำกัด นี้หมายความว่าคุณไม่สามารถใช้ id () เพื่อสร้างแฮชของวัตถุเพื่อจัดเก็บได้อย่างปลอดภัยปลดปล่อยมันและนำกลับมาใช้ใหม่ในภายหลัง
Joshua Clayton

71

คุณสามารถปรับใช้การเริ่มต้นใหม่ได้ด้วยวิธีนี้:

def __repr__(self):
    return '<%s.%s object at %s>' % (
        self.__class__.__module__,
        self.__class__.__name__,
        hex(id(self))
    )

1
ฉันรู้ว่ามันเก่า แต่คุณสามารถทำreturn object.__repr__(self)หรือแม้กระทั่งทำobject.__repr__(obj)ทุกครั้งที่คุณต้องการสิ่งนี้แทนที่จะทำคลาสใหม่
Artyer

2
@Artyer: ความคิดเห็นนี้เกี่ยวข้องกับคำถามเดิมอย่างไร คำตอบที่โพสต์ที่นี่สร้างที่อยู่ใหม่ตามที่ร้องขอโดยคำถามดั้งเดิม คุณจะไม่ต้องกัดสายถ้าคุณทำอย่างที่คุณแนะนำ?
Rafe

1
ดูเหมือนว่าคำตอบที่ดีที่สุดสำหรับฉัน เพียงลองสร้างวัตถุ () พิมพ์แล้วพิมพ์ hex (id (วัตถุ)) และผลลัพธ์ที่ตรงกัน
Rafe

@Rafe คำตอบของคุณเป็นวิธีที่ยืดยาว__repr__ = object.__repr__และไม่ได้เป็นข้อพิสูจน์ที่โง่เขลาเพราะมีหลายสถานการณ์ที่สิ่งนี้ไม่ได้ผลเช่นการใช้งานแบบ overrided __getattribute__หรือ non-CPython ที่ id ไม่ ตำแหน่งหน่วยความจำ มันยังไม่เติม z ดังนั้นคุณจะต้องคิดออกหากระบบเป็น 64 บิตและเพิ่มศูนย์ตามความจำเป็น
Artyer

@Artyer: ตัวอย่างของฉันแสดงวิธีการสร้างการตอบโต้ เรามักจะเพิ่มข้อมูลที่กำหนดเอง (และฉันจะบอกว่านี่เป็นวิธีการเข้ารหัสที่ดีเพราะช่วยในการแก้ไขข้อบกพร่อง) เราใช้สไตล์นี้อย่างหนักและฉันไม่เคยพบเห็นเคสแบบขอบของคุณเลย ขอบคุณสำหรับการแบ่งปัน!
Rafe


24

มีปัญหาเล็กน้อยที่นี่ซึ่งไม่ครอบคลุมโดยคำตอบอื่น ๆ

ก่อนอื่นให้idส่งคืนเท่านั้น:

“ เอกลักษณ์” ของวัตถุ นี่คือเลขจำนวนเต็ม (หรือจำนวนเต็มแบบยาว) ซึ่งรับประกันว่าจะไม่ซ้ำกันและคงที่สำหรับวัตถุนี้ในช่วงชีวิตของมัน วัตถุสองชิ้นที่มีอายุการใช้งานไม่ทับซ้อนกันอาจมีid()ค่าเท่ากัน


ใน CPython สิ่งนี้เกิดขึ้นเป็นตัวชี้ไปยังPyObjectที่แสดงถึงวัตถุในล่ามซึ่งเป็นสิ่งเดียวกันกับที่object.__repr__แสดง แต่นี่เป็นเพียงรายละเอียดการนำไปปฏิบัติของ CPython ไม่ใช่สิ่งที่เป็นจริงของ Python โดยทั่วไป Jython ไม่ได้เกี่ยวข้องกับพอยน์เตอร์มันเกี่ยวข้องกับการอ้างอิง Java (ซึ่งแน่นอนว่า JVM อาจแสดงถึงเป็นพอยน์เตอร์ แต่คุณมองไม่เห็น - และไม่ต้องการเพราะ GC อนุญาตให้ย้ายพวกมันไป) PyPy ช่วยให้ประเภทที่แตกต่างมีประเภทที่แตกต่างกันidแต่โดยทั่วไปส่วนใหญ่เป็นเพียงดัชนีไปยังสารบัญที่คุณเรียกidซึ่งเห็นได้ชัดว่าจะไม่เป็นตัวชี้ ฉันไม่แน่ใจเกี่ยวกับ IronPython แต่ฉันสงสัยว่ามันเป็น Jython มากกว่าเหมือนกับ CPython ในเรื่องนี้ ดังนั้นในการใช้งาน Python ส่วนใหญ่ไม่มีทางที่จะทำให้สิ่งใดปรากฏในนั้นreprและไม่มีประโยชน์ถ้าคุณทำ


แต่ถ้าคุณเป็นเพียงแค่สนใจเกี่ยวกับ CPython? นั่นเป็นกรณีที่ค่อนข้างธรรมดาหลังจากทั้งหมด

ก่อนอื่นคุณอาจสังเกตเห็นว่าidเป็นจำนวนเต็ม; * ถ้าคุณต้องการ0x2aba1c0cf890สตริงนั้นแทนที่จะเป็นตัวเลข46978822895760คุณจะต้องจัดรูปแบบด้วยตัวคุณเอง ภายใต้ครอบคลุมผมเชื่อว่าobject.__repr__ในท้ายที่สุดคือการใช้printfของ%pรูปแบบที่คุณไม่ได้จากงูใหญ่ ... แต่คุณสามารถทำสิ่งนี้:

format(id(spam), '#010x' if sys.maxsize.bit_length() <= 32 else '#18x')

* ใน 3.x intก็เป็น ใน 2.x มันเป็นเรื่องintที่ใหญ่พอที่จะถือตัวชี้ซึ่งอาจไม่ใช่เพราะปัญหาหมายเลขที่ลงนามในบางแพลตฟอร์มและlongอย่างอื่น

มีอะไรที่คุณสามารถทำได้กับพอยน์เตอร์เหล่านี้นอกเหนือจากพิมพ์ออกมา? แน่นอน (อีกครั้งสมมติว่าคุณให้ความสำคัญกับ CPython เท่านั้น)

ทั้งหมดของC APIฟังก์ชั่นใช้ตัวชี้ไปที่PyObjectหรือประเภทที่เกี่ยวข้อง สำหรับประเภทที่เกี่ยวข้องเหล่านั้นคุณก็สามารถโทรPyFoo_Checkเพื่อให้แน่ใจว่าจริงๆมันเป็นวัตถุโยนแล้วด้วยFoo (PyFoo *)pดังนั้นหากคุณกำลังเขียนส่วนขยาย C idสิ่งที่คุณต้องการก็คือ

ถ้าคุณกำลังเขียนโค้ด Python แท้ คุณสามารถเรียกฟังก์ชั่นเดียวกันกับจากpythonapictypes


สุดท้ายไม่กี่คำตอบอื่น ๆ ctypes.addressofได้นำขึ้นมา ไม่เกี่ยวข้องที่นี่ ใช้งานได้เฉพาะกับctypesวัตถุเช่นc_int32(และอาจเป็นวัตถุคล้ายบัฟเฟอร์หน่วยความจำบางอย่างเช่นที่จัดทำโดยnumpy) และถึงตรงนั้นมันไม่ได้ให้ที่อยู่ของc_int32คุณค่ากับคุณ แต่ให้ที่อยู่ของ C-level int32ที่c_int32ล้อมรอบ

ที่ถูกกล่าวบ่อยกว่าถ้าคุณคิดว่าคุณต้องการที่อยู่ของบางสิ่งบางอย่างคุณไม่ต้องการวัตถุหลามพื้นเมืองในตอนแรกคุณต้องการctypesวัตถุ


อย่างนี้เป็นวิธีเดียวที่จะเก็บวัตถุที่ไม่แน่นอนในแผนที่ / ชุดเมื่อตัวตนมีความสำคัญ ...
Enerccio

@Enerccio การใช้อื่น ๆ ของid- รวมถึงการใช้เพื่อเก็บค่าที่ไม่แน่นอนในseenชุดหรือcachedict - ไม่ขึ้นอยู่กับวิธีidการชี้หรือเกี่ยวข้องในทางใดทางreprหนึ่ง ซึ่งเป็นสาเหตุที่รหัสดังกล่าวทำงานในการใช้งาน Python ทั้งหมดแทนที่จะทำงานใน CPython เท่านั้น
abarnert

ใช่ฉันใช้idมัน แต่ฉันหมายถึงแม้ใน java คุณสามารถรับที่อยู่ของวัตถุดูเหมือนแปลกไม่มีทางใน (C) Python ตั้งแต่ที่มีเสถียรภาพจริง gc ที่จะไม่ย้ายวัตถุดังนั้นที่อยู่ยังคงเหมือนเดิม
Enerccio

@Eccccio แต่คุณไม่ต้องการใช้ที่อยู่ของวัตถุสำหรับค่าแคช - คุณต้องการใช้idวัตถุสำหรับไม่ว่าจะเป็นที่อยู่หรือไม่ ตัวอย่างเช่นใน PyPy idยังคงมีประโยชน์เหมือนกับกุญแจสำคัญใน CPython แม้ว่าโดยปกติแล้วจะเป็นเพียงดัชนีในตารางที่ซ่อนอยู่บางส่วนในการนำไปใช้ แต่ตัวชี้จะไร้ประโยชน์เพราะ (เช่น Java) วัตถุสามารถเคลื่อนย้ายได้ หน่วยความจำ
abarnert

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

13

เพียงเพื่อตอบสนองต่อ Torsten ฉันไม่สามารถโทรหาaddressof()วัตถุหลามธรรมดาได้ นอกจากนี้, id(a) != addressof(a). สิ่งนี้อยู่ใน CPython ไม่รู้อะไรเลย

>>> from ctypes import c_int, addressof
>>> a = 69
>>> addressof(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: invalid type
>>> b = c_int(69)
>>> addressof(b)
4300673472
>>> id(b)
4300673392

4

ด้วยctypesคุณสามารถทำสิ่งเดียวกันได้

>>> import ctypes
>>> a = (1,2,3)
>>> ctypes.addressof(a)
3077760748L

เอกสารอ้างอิง:

addressof(C instance) -> integer
ส่งคืนที่อยู่ของบัฟเฟอร์ภายใน C อินสแตนซ์

โปรดทราบว่าใน CPython ปัจจุบันid(a) == ctypes.addressof(a)แต่ctypes.addressofควรส่งคืนที่อยู่จริงสำหรับการใช้งาน Python แต่ละรายการหาก

  • รองรับ ctypes
  • พอยน์เตอร์หน่วยความจำเป็นแนวคิดที่ถูกต้อง

แก้ไข : เพิ่มข้อมูลเกี่ยวกับล่ามอิสระของ ctypes


13
>>> import ctypes >>> a = (1,2,3) >>> ctypes.addressof (a) Traceback (การโทรล่าสุดครั้งล่าสุด): ไฟล์ "<input>", บรรทัด 1, ใน <โมดูล> TypeError: ประเภทที่ไม่ถูกต้อง >>> id (a) 4493268872 >>>

5
ฉันเห็นด้วยกับ Barry: โค้ดข้างต้นส่งผลให้TypeError: invalid typeเมื่อฉันลองกับ Python 3.4
แบรนดอนโรดส์


1

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ถ้าคุณยังคงเขียนโปรแกรมในหลาม 3 วันนี้ ... ฉันได้พบจริงว่าถ้ามันเป็นสตริงแล้วมีวิธีที่ง่ายจริง ๆ :

>>> spam.upper
<built-in method upper of str object at 0x1042e4830>
>>> spam.upper()
'YO I NEED HELP!'
>>> id(spam)
4365109296

การแปลงสตริงไม่มีผลกับตำแหน่งในหน่วยความจำ:

>>> spam = {437 : 'passphrase'}
>>> object.__repr__(spam)
'<dict object at 0x1043313f0>'
>>> str(spam)
"{437: 'passphrase'}"
>>> object.__repr__(spam)
'<dict object at 0x1043313f0>'

0

ในขณะที่มันเป็นความจริงที่id(object)ได้รับที่อยู่ของวัตถุในการใช้งานเริ่มต้นของ CPython โดยทั่วไปจะไร้ประโยชน์ ... คุณไม่สามารถทำอะไรกับที่อยู่จากรหัส Python บริสุทธิ์

ครั้งเดียวที่คุณจะสามารถใช้ที่อยู่ได้จริงคือจากไลบรารีส่วนขยาย C ... ซึ่งในกรณีนี้มันเป็นเรื่องเล็กน้อยที่จะได้รับที่อยู่ของวัตถุเนื่องจากวัตถุ Python จะถูกส่งผ่านเป็นตัวชี้ C เสมอ


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