แฮชทำอะไรในไพ ธ อน?


88

ฉันเห็นตัวอย่างของโค้ดที่hashใช้ฟังก์ชันกับทูเพิล ด้วยเหตุนี้จึงส่งคืนจำนวนเต็มลบ ฉันสงสัยว่าฟังก์ชั่นนี้ทำหน้าที่อะไร? Google ไม่ช่วย ฉันพบหน้าที่อธิบายวิธีคำนวณแฮช แต่ไม่ได้อธิบายว่าทำไมเราถึงต้องการฟังก์ชันนี้


8
คุณดูเอกสาร
ไหม

ไปที่ลิงค์นี้ (เอกสารอย่างเป็นทางการ) มันระบุทุกอย่าง ไปที่ลิงค์ !
tailor_raj

2
ฉันชอบที่คำถามไม่ซ้ำว่า "มันคืออะไร" แต่เป็น "ทำไมเราถึงต้องการ"
dnozay

ลิงก์อย่างเป็นทางการสับสนมาก
Rasmi Ranjan Nayak

คำตอบ:


156

กัญชาเป็นจำนวนเต็มขนาดคงที่ที่ระบุค่าเฉพาะ แต่ละค่าต้องมีแฮชของตัวเองดังนั้นสำหรับค่าเดียวกันคุณจะได้รับแฮชเดียวกันแม้ว่าจะไม่ใช่อ็อบเจกต์เดียวกันก็ตาม

>>> hash("Look at me!")
4343814758193556824
>>> f = "Look at me!"
>>> hash(f)
4343814758193556824

ต้องสร้างค่าแฮชในลักษณะที่กระจายค่าผลลัพธ์อย่างเท่าเทียมกันเพื่อลดจำนวนการชนกันของแฮชที่คุณได้รับ การชนกันของแฮชคือเมื่อค่าสองค่าที่แตกต่างกันมีแฮชเหมือนกัน ดังนั้นการเปลี่ยนแปลงที่ค่อนข้างเล็กมักส่งผลให้แฮชต่างกันมาก

>>> hash("Look at me!!")
6941904779894686356

ตัวเลขเหล่านี้มีประโยชน์มากเนื่องจากสามารถค้นหาค่าได้อย่างรวดเร็วในคอลเล็กชันค่าจำนวนมาก สองตัวอย่างของการใช้งานคือ Python setและdict. ในlistถ้าคุณต้องการที่จะตรวจสอบว่ามีค่าอยู่ในรายการที่มีif x in values:งูใหญ่ต้องผ่านรายการทั้งหมดและเปรียบเทียบกับค่าในแต่ละรายการx นี้สามารถใช้เวลานานเป็นเวลานานvalues listในsetPython จะติดตามแฮชแต่ละตัวและเมื่อคุณพิมพ์if x in values:Python จะได้รับค่าแฮชสำหรับxค้นหาในโครงสร้างภายในจากนั้นเปรียบเทียบxกับค่าที่มีแฮชเดียวกันxเท่านั้น

วิธีการเดียวกันนี้ใช้สำหรับการค้นหาพจนานุกรม ทำให้การค้นหาsetและdictรวดเร็วมากในขณะที่การค้นหาlistช้า นอกจากนี้ยังหมายความว่าคุณสามารถมีอ็อบเจ็กต์ที่ไม่สามารถแฮชได้ใน a listแต่ไม่มีใน a setหรือเป็นคีย์ในไฟล์dict. ตัวอย่างทั่วไปของอ็อบเจ็กต์ที่ไม่สามารถแฮชได้คืออ็อบเจ็กต์ใด ๆ ที่ไม่แน่นอนซึ่งหมายความว่าคุณสามารถเปลี่ยนค่าได้ หากคุณมีอ็อบเจกต์ที่เปลี่ยนแปลงได้ไม่ควรแฮชเนื่องจากแฮชของมันจะเปลี่ยนไปตลอดอายุการใช้งานซึ่งจะทำให้เกิดความสับสนอย่างมากเนื่องจากอ็อบเจ็กต์อาจอยู่ภายใต้ค่าแฮชที่ไม่ถูกต้องในพจนานุกรม

โปรดทราบว่าแฮชของค่าจะต้องเหมือนกันสำหรับการรัน Python เพียงครั้งเดียว ใน Python 3.3 จริง ๆ แล้วพวกเขาจะเปลี่ยนทุกครั้งที่รัน Python ใหม่:

$ /opt/python33/bin/python3
Python 3.3.2 (default, Jun 17 2013, 17:49:21) 
[GCC 4.6.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("foo")
1849024199686380661
>>> 
$ /opt/python33/bin/python3
Python 3.3.2 (default, Jun 17 2013, 17:49:21) 
[GCC 4.6.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("foo")
-7416743951976404299

สิ่งนี้จะทำให้ยากที่จะคาดเดาว่าค่าแฮชที่สตริงหนึ่ง ๆ จะมีค่าอะไรซึ่งเป็นคุณสมบัติด้านความปลอดภัยที่สำคัญสำหรับเว็บแอปพลิเคชันเป็นต้น

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


12
เกี่ยวกับการชนแฮชที่อาจเกิดขึ้น: hash(-1) == hash(-2)(runnin Python 2.7)
Matthias

2
ฉันใช้ Python 3.6.1 และมีการชนกัน
The_Martian

hash(-1) == hash(-2)ยังคงมีอยู่ในปัจจุบัน โชคดีที่มันไม่ส่งผลเสียต่อพจนานุกรมและการตั้งค่าการค้นหา ทั้งหมดจำนวนเต็มอื่น ๆiการแก้ปัญหาให้ตัวเองสำหรับการยกเว้นhash(i) -1
Chris Conlan

36

TL; DR:

โปรดดูอภิธานศัพท์ : hash()ใช้เป็นทางลัดในการเปรียบเทียบอ็อบเจกต์โดยอ็อบเจ็กต์จะถือว่าแฮชได้หากเปรียบเทียบกับอ็อบเจ็กต์อื่น hash()นั่นคือเหตุผลที่เราใช้ นอกจากนี้ยังใช้ในการเข้าถึงdictและsetองค์ประกอบที่จะดำเนินการตามตารางกัญชาปรับขนาดได้ใน CPython

ข้อพิจารณาทางเทคนิค

  • โดยปกติการเปรียบเทียบวัตถุ (ซึ่งอาจเกี่ยวข้องกับการเรียกซ้ำหลายระดับ) มีราคาแพง
  • โดยเฉพาะอย่างยิ่งhash()ฟังก์ชั่นเป็นลำดับความสำคัญ (หรือหลายอย่าง) ที่มีราคาไม่แพง
  • การเปรียบเทียบแฮชสองอันนั้นง่ายกว่าการเปรียบเทียบสองอ็อบเจกต์ซึ่งเป็นที่ที่ทางลัดอยู่

หากคุณอ่านเกี่ยวกับวิธีการนำพจนานุกรมมาใช้พวกเขาใช้ตารางแฮชซึ่งหมายความว่าการได้มาซึ่งคีย์จากอ็อบเจกต์นั้นเป็นมุมหินในการดึงอ็อบเจกต์ในพจนานุกรมในO(1). ว่า แต่มากขึ้นอยู่กับฟังก์ชันแฮชของคุณจะมีการปะทะกันทน กรณีที่เลวร้ายที่สุดสำหรับการรับรายการO(n)ในพจนานุกรมเป็นจริง

ในหมายเหตุนั้นโดยปกติแล้ววัตถุที่เปลี่ยนแปลงไม่ได้ คุณสมบัติที่แฮชได้หมายความว่าคุณสามารถใช้อ็อบเจกต์เป็นคีย์ได้ หากใช้ค่าแฮชเป็นคีย์และเนื้อหาของอ็อบเจ็กต์เดียวกันนั้นเปลี่ยนไปฟังก์ชันแฮชควรจะคืนค่าอะไร เป็นคีย์เดียวกันหรือคนละคีย์? มันขึ้นอยู่กับว่าคุณกำหนดฟังก์ชันแฮชของคุณ

การเรียนรู้ตามตัวอย่าง:

ลองนึกภาพว่าเรามีคลาสนี้:

>>> class Person(object):
...     def __init__(self, name, ssn, address):
...         self.name = name
...         self.ssn = ssn
...         self.address = address
...     def __hash__(self):
...         return hash(self.ssn)
...     def __eq__(self, other):
...         return self.ssn == other.ssn
... 

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

และเรามี Bob:

>>> bob = Person('bob', '1111-222-333', None)

บ็อบไปพบผู้พิพากษาเพื่อเปลี่ยนชื่อ:

>>> jim = Person('jim bo', '1111-222-333', 'sf bay area')

นี่คือสิ่งที่เรารู้:

>>> bob == jim
True

แต่นี่เป็นวัตถุสองชิ้นที่แตกต่างกันโดยมีการจัดสรรหน่วยความจำที่แตกต่างกันเช่นเดียวกับบันทึกที่แตกต่างกันสองรายการของบุคคลเดียวกัน:

>>> bob is jim
False

มาถึงส่วนที่แฮช () มีประโยชน์:

>>> dmv_appointments = {}
>>> dmv_appointments[bob] = 'tomorrow'

เดาอะไร:

>>> dmv_appointments[jim] #?
'tomorrow'

จากบันทึกที่แตกต่างกันสองรายการคุณสามารถเข้าถึงข้อมูลเดียวกันได้ ลองทำสิ่งนี้:

>>> dmv_appointments[hash(jim)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in __eq__
AttributeError: 'int' object has no attribute 'ssn'
>>> hash(jim) == hash(hash(jim))
True

เกิดอะไรขึ้น? นั่นคือการปะทะกัน เนื่องจากhash(jim) == hash(hash(jim))ซึ่งเป็นจำนวนเต็ม btw ทั้งคู่เราจึงต้องเปรียบเทียบอินพุตของ__getitem__รายการทั้งหมดที่ชนกัน builtin intไม่มีssnแอตทริบิวต์จึงเดินทาง

>>> del Person.__eq__
>>> dmv_appointments[bob]
'tomorrow'
>>> dmv_appointments[jim]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: <__main__.Person object at 0x7f611bd37110>

ในตัวอย่างสุดท้ายนี้ฉันแสดงให้เห็นว่าแม้จะมีการชนกัน แต่การเปรียบเทียบจะดำเนินการ แต่วัตถุก็ไม่เท่ากันอีกต่อไปซึ่งหมายความว่ามันสามารถยก a KeyError.


คำอธิบายที่มีประโยชน์จริงๆ ในฐานะมือใหม่สิ่งนี้ช่วยให้ฉันคิดหาวิธีสร้างชั้นเรียนที่สามารถใส่เป็นชุดและใช้เป็นคีย์สำหรับพจนานุกรม / ตารางแฮชได้ นอกจากนี้ถ้าฉันรวบรวม [hashable_obj] = hashable_obj ฉันจะได้รับตัวชี้ไปยังอินสแตนซ์นั้นในภายหลัง แต่อย่าบอกฉันว่ามีวิธีที่ดีกว่าในการติดตามคอลเล็กชันดังกล่าวหรือไม่
PaulDong

@dnozay แต่ถึงกระนั้นผลลัพธ์ของhash()เป็นจำนวนเต็มขนาดคงที่ซึ่งอาจทำให้เกิดการชนกันได้
เปลี่ยนแปลงมากเกินไปเมื่อ

2
ใครสามารถอธิบายรายละเอียดเกี่ยวกับการใช้งาน__eq__ในตัวอย่างด้านบนได้ พจนานุกรมเรียกว่าเมื่อพยายามเปรียบเทียบคีย์ที่ได้รับกับคีย์ทั้งหมดที่มีหรือไม่? ดังกล่าวว่าโดยวิธีการในตัวอย่างที่ผ่านมาในพจนานุกรมมีอะไรจะโทรเพื่อใช้ในการตรวจสอบเทียบเท่าของคีย์จะได้รับการต้อนรับด้วยกุญแจจะมีหรือไม่ del__eq__
Jet Blue

1
@JetBlue ว่า "collosion" hash(jim)อธิบายไม่สมบูรณ์ในตัวอย่างที่มีคีย์ Person.__eq__ถูกเรียกเนื่องจากคีย์ที่มีอยู่มีแฮชเหมือนกันhash(jim)เพื่อให้แน่ใจว่าคีย์Person.__eq__นี้ถูกใช้ มันผิดพลาดเพราะถือว่านั่นotherคือintมีssnแอตทริบิวต์ หากไม่มีhash(jim)คีย์ในพจนานุกรม__eq__จะไม่ถูกเรียก สิ่งนี้อธิบายว่าเมื่อการค้นหาคีย์สามารถเป็น O (n): เมื่อรายการทั้งหมดมีแฮชเดียวกัน__eq__ต้องใช้กับรายการทั้งหมดเช่นในกรณีที่ไม่มีคีย์
WloHu

1
แม้ว่าฉันจะเข้าใจถึงความสนใจด้านการเรียนการสอนของตัวอย่างของคุณ แต่การเขียนจะง่ายกว่าไหมโดยไม่dmv_appointments[bob.ssn] = 'tomorrow'จำเป็นต้องกำหนด__hash__วิธีการ ฉันเข้าใจว่าเพิ่มอักขระ 4 ตัวสำหรับการนัดหมายทุกครั้งที่คุณเขียนและอ่าน แต่ดูเหมือนว่าจะชัดเจนกว่าสำหรับฉัน
Alexis

3

เอกสารhash() Python สำหรับสถานะ:

ค่าแฮชเป็นจำนวนเต็ม ใช้เพื่อเปรียบเทียบคีย์พจนานุกรมอย่างรวดเร็วในระหว่างการค้นหาพจนานุกรม

พจนานุกรม Python ถูกนำไปใช้เป็นตารางแฮช ดังนั้นทุกครั้งที่คุณใช้พจนานุกรมระบบhash()จะเรียกคีย์ที่คุณส่งผ่านเพื่อมอบหมายงานหรือค้นหา

นอกจากนี้เอกสารสำหรับdictสถานะประเภท :

ค่าที่ไม่สามารถแฮชได้นั่นคือค่าที่มีรายการพจนานุกรมหรือประเภทอื่น ๆ ที่เปลี่ยนแปลงได้ (ซึ่งเปรียบเทียบตามค่าแทนที่จะเป็นเอกลักษณ์ของอ็อบเจ็กต์) ไม่สามารถใช้เป็นคีย์ได้


1

กัญชาถูกใช้โดยพจนานุกรมและชุดเพื่อค้นหาวัตถุอย่างรวดเร็ว เป็นจุดที่ดีเริ่มต้นคือบทความวิกิพีเดียในตารางแฮช


-2

คุณสามารถใช้Dictionaryชนิดข้อมูลใน python คล้ายกับแฮชมาก - และยังรองรับการซ้อนกันคล้ายกับแฮชที่ซ้อนกัน

ตัวอย่าง:

dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
dict['Age'] = 8; # update existing entry
dict['School'] = "DPS School" # Add new entry

print ("dict['Age']: ", dict['Age'])
print ("dict['School']: ", dict['School'])

สำหรับข้อมูลเพิ่มเติมโปรดอ้างอิงนี้สอนเกี่ยวกับชนิดข้อมูลพจนานุกรม

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