ฉันควรใช้คลาสหรือพจนานุกรม?


105

ฉันมีคลาสที่มีเฉพาะฟิลด์และไม่มีเมธอดเช่นนี้:

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

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


2
ฉันมักจะใช้พจนานุกรมในการเก็บรักษาข้อมูลและดูเหมือนว่าเป็นกรณีการใช้งานสำหรับฉัน ในบางกรณีการได้รับคลาสจากdictcan เหมาะสมถึง ข้อได้เปรียบที่เป็นระเบียบ: เมื่อคุณแก้ไขข้อบกพร่องเพียงแค่พูดprint(request)และคุณจะเห็นข้อมูลสถานะทั้งหมดได้ทันที ด้วยวิธีการที่คลาสสิกกว่านี้คุณจะต้องเขียน__str__วิธีการแบบกำหนดเองของคุณซึ่งแย่มากถ้าคุณต้องทำมันตลอดเวลา
ไหล

พวกมันใช้แทนกันได้stackoverflow.com/questions/1305532/…
Oleksiy

ถ้าชั้นเรียนมีความหมายโดยรวมและชัดเจนสำหรับผู้อื่นทำไมไม่ นอกจากนี้หากคุณกำหนดคลาสจำนวนมากด้วยอินเทอร์เฟซทั่วไปทำไมไม่? แต่ Python ไม่สนับสนุนแนวคิดเชิงวัตถุอันยิ่งใหญ่เช่น C ++
MasterControlProgram

4
@ ครึ่ง OOP หลามไม่รองรับอะไร
qwr

คำตอบ:


34

ทำไมคุณถึงสร้างพจนานุกรมนี้ ข้อดีคืออะไร? จะเกิดอะไรขึ้นหากคุณต้องการเพิ่มโค้ดในภายหลัง __init__รหัสของคุณจะไปที่ไหน

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

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

เก็บคลาสนี้ไว้


ฉันจะสร้างเมธอดโรงงานประเภทหนึ่งโดยสร้าง dict แทน__init__เมธอดของคลาส แต่คุณพูดถูก: ฉันจะแยกสิ่งที่อยู่ด้วยกัน
deamon

89
ไม่เห็นด้วยกับคุณอีกต่อไป: พจนานุกรมชุดรายการและสิ่งที่รวมอยู่ในกลุ่มข้อมูลที่เกี่ยวข้อง ไม่มีข้อสันนิษฐานใด ๆ ว่าค่าของพจนานุกรมควรหรือต้องเป็นประเภทข้อมูลเดียวกันในทางตรงกันข้าม ในหลายรายการและชุดค่าจะมีประเภทเดียวกัน แต่ส่วนใหญ่เป็นเพราะนั่นคือสิ่งที่เราต้องการแจกแจงร่วมกัน ที่จริงฉันคิดว่าการใช้คลาสเพื่อเก็บข้อมูลอย่างแพร่หลายเป็นการใช้อุบายในทางที่ผิด เมื่อคุณคิดถึงปัญหาการทำให้เป็นอนุกรมคุณสามารถดูสาเหตุได้อย่างง่ายดาย
ไหล

4
เป็นการเขียนโปรแกรมเชิง OBJECT และไม่ใช่คลาสที่มุ่งเน้นด้วยเหตุผล: เราจัดการกับวัตถุ วัตถุมีลักษณะ 2 (3) คุณสมบัติ: 1. สถานะ (สมาชิก) 2. พฤติกรรม (วิธีการ) และ 3. อินสแตนซ์สามารถอธิบายได้หลายคำ ดังนั้นคลาสจึงมีไว้สำหรับการรวมสถานะและพฤติกรรมเข้าด้วยกัน
friendzis

14
ฉันได้ทำเครื่องหมายสิ่งนี้ไว้เนื่องจากคุณควรตั้งค่าเริ่มต้นเป็นโครงสร้างข้อมูลที่ง่ายกว่าเสมอหากเป็นไปได้ ในกรณีนี้พจนานุกรมจะเพียงพอสำหรับวัตถุประสงค์ที่ตั้งไว้ คำถามwhere would your __init__ code go?เกี่ยวกับ อาจชักชวนนักพัฒนาที่มีประสบการณ์น้อยว่าต้องใช้เฉพาะคลาสเนื่องจากไม่มีการใช้วิธีการinitในพจนานุกรม ไร้สาระ
Lloyd Moore

1
@Ralf คลาส Foo เป็นเพียงประเภทเช่น int และ string คุณเก็บค่าในประเภทจำนวนเต็มหรือตัวแปร foo ของประเภทจำนวนเต็มหรือไม่? ความแตกต่างทางความหมายที่ลึกซึ้ง แต่สำคัญ ในภาษาเช่น C ความแตกต่างนี้ไม่เกี่ยวข้องกับภายนอกวงวิชาการมากเกินไป แม้ว่าภาษา OO ส่วนใหญ่จะมีการรองรับตัวแปรคลาสซึ่งทำให้ความแตกต่างระหว่างคลาสและอ็อบเจ็กต์ / อินสแตนซ์มีความเกี่ยวข้องอย่างมาก - คุณจัดเก็บข้อมูลในคลาส (แชร์ข้ามอินสแตนซ์ทั้งหมด) หรือในออบเจ็กต์เฉพาะหรือไม่ อย่าให้โดนกัด
friendzis

44

ใช้พจนานุกรมเว้นแต่คุณต้องการกลไกพิเศษของคลาส นอกจากนี้คุณยังสามารถใช้namedtupleสำหรับแนวทางไฮบริด:

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

ความแตกต่างของประสิทธิภาพที่นี่จะน้อยมากแม้ว่าฉันจะแปลกใจถ้าพจนานุกรมไม่เร็วขึ้น


15
"ความแตกต่างของผลการดำเนินงานที่นี่จะน้อยที่สุดถึงแม้ว่าฉันจะประหลาดใจถ้าพจนานุกรมไม่ได้อย่างมีนัยสำคัญได้เร็วขึ้น." สิ่งนี้ไม่ได้คำนวณ :)
mipadi

1
@mipadi: นี่เป็นเรื่องจริง แก้ไขแล้ว: p
Katriel

Dict เร็วกว่า Nametuple 1.5 เท่าและเร็วกว่าคลาสที่ไม่มีสล็อตถึงสองเท่า ตรวจสอบโพสต์ของฉันเกี่ยวกับคำตอบนี้
alexpinho98

@ alexpinho98: ฉันพยายามอย่างหนักเพื่อค้นหา 'โพสต์' ที่คุณอ้างถึง แต่หาไม่เจอ! คุณสามารถระบุ URL ขอบคุณ!
Dan Oblinger

@ DanOblinger ฉันสมมติว่าเขาหมายถึงคำตอบของเขาด้านล่าง
Adam Lewis

37

คลาสใน python คือ dict ที่อยู่ข้างใต้ คุณได้รับค่าใช้จ่ายบางอย่างจากพฤติกรรมของชั้นเรียน แต่คุณจะไม่สามารถสังเกตเห็นได้หากไม่มีผู้สร้างโปรไฟล์ ในกรณีนี้ฉันเชื่อว่าคุณได้รับประโยชน์จากชั้นเรียนเนื่องจาก:

  • ตรรกะทั้งหมดของคุณอาศัยอยู่ในฟังก์ชันเดียว
  • ง่ายต่อการอัปเดตและยังคงห่อหุ้มอยู่
  • หากคุณเปลี่ยนแปลงอะไรในภายหลังคุณสามารถทำให้อินเทอร์เฟซเหมือนเดิมได้อย่างง่ายดาย

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

26

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

ฉันเปรียบเทียบเวลาที่ใช้ในการสร้างและเปลี่ยนตัวแปรใน dict คลาส new_style และคลาส new_style กับสล็อต

นี่คือรหัสที่ฉันใช้ในการทดสอบ (มันค่อนข้างยุ่ง แต่ก็ใช้งานได้)

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

และนี่คือผลลัพธ์ ...

การสร้าง...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

การเปลี่ยนตัวแปร ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

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

บันทึก:

ฉันทดสอบ 'คลาส' ทั้งคลาส new_style และ old_style ปรากฎว่าคลาส old_style สร้างได้เร็วกว่า แต่แก้ไขได้ช้ากว่า (ไม่มาก แต่สำคัญถ้าคุณสร้างคลาสจำนวนมากแบบวนซ้ำ (เคล็ดลับ: คุณทำผิด))

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

แก้ไข:

ในภายหลังฉันได้ทดสอบ namedtuple: ฉันไม่สามารถแก้ไขได้ แต่ในการสร้างตัวอย่าง 10,000 ตัวอย่าง (หรืออะไรทำนองนั้น) ใช้เวลา 1.4 วินาทีดังนั้นพจนานุกรมจึงเร็วที่สุด

ถ้าฉันเปลี่ยนฟังก์ชัน dictเพื่อรวมคีย์และค่าและส่งคืนคำสั่งแทนตัวแปรที่มี dict เมื่อฉันสร้างมันจะให้0.65 แทน 0.8 วินาที

class Foo(dict):
    pass

สร้างเป็นเหมือนชั้นเรียนกับสล็อตและการเปลี่ยนแปลงตัวแปรที่ช้าที่สุด (0.17 วินาที) ดังนั้นไม่ใช้ชั้นเรียนเหล่านี้ ไปที่ dict (speed) หรือสำหรับคลาสที่ได้มาจาก object ('syntax candy')


ฉันต้องการดูตัวเลขสำหรับคลาสย่อยของdict(โดยไม่มีวิธีการลบล้างฉันเดา?) มันทำงานในลักษณะเดียวกับคลาสรูปแบบใหม่ที่เขียนขึ้นใหม่หรือไม่?
Benjamin Hodgson

13

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

เมื่อใช้ Python สิ่งสำคัญคือต้องสร้างอย่างมีวินัยเนื่องจากภาษาช่วยให้ผู้เขียนสามารถถ่ายภาพตัวเองได้หลายวิธี


9
คำตอบเกี่ยวกับ SO จะเรียงตามคะแนนโหวตและคำตอบที่มีการนับคะแนนเท่ากันจะเรียงลำดับแบบสุ่ม ดังนั้นโปรดชี้แจงว่าคุณหมายถึงใครด้วย "ผู้โพสต์สุดท้าย"
Mike DeSimone

4
และคุณจะรู้ได้อย่างไรว่าสิ่งที่มีเฉพาะเขตข้อมูลและไม่มีฟังก์ชันคือ "วัตถุ" ในความหมาย OO?
Muhammad Alkarouri

1
การทดสอบกระดาษลิตมัสของฉันคือ "โครงสร้างของข้อมูลนี้ได้รับการแก้ไขแล้วหรือไม่" ถ้าใช่ให้ใช้วัตถุหรือไม่ก็ใช้คำสั่ง การออกไปจากนี้มี แต่จะสร้างความสับสน
weberc2

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

5

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

ดังนั้นจงยึดติดกับชั้นเรียน


2
ฉันไม่เห็นด้วยอย่างยิ่ง ไม่มีเหตุผลที่จะ จำกัด การใช้พจนานุกรมเฉพาะสิ่งที่ต้องทำซ้ำ มีไว้เพื่อรักษาการทำแผนที่
Katriel

1
โปรดอ่านอย่างละเอียดอีกเล็กน้อย ผมบอกว่าอาจต้องการที่จะห่วงมากกว่าไม่ได้จะ สำหรับฉันแล้วการใช้พจนานุกรมบอกเป็นนัยว่ามีความคล้ายคลึงกันระหว่างคีย์และค่า ตัวอย่างเช่นชื่อที่มีอายุ การใช้คลาสจะทำให้ 'คีย์' ต่างๆมีค่าที่มีความหมายแตกต่างกันอย่างมากมาย ตัวอย่างเช่นใช้คลาส PErson ในที่นี้ "ชื่อ" จะเป็นสตริงและ "เพื่อน" อาจเป็นรายการหรือพจนานุกรมหรือวัตถุอื่น ๆ ที่เหมาะสม คุณจะไม่วนซ้ำแอตทริบิวต์เหล่านี้ทั้งหมดเป็นส่วนหนึ่งของการใช้งานปกติของคลาสนี้
ตราบาป

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

4

หากสิ่งที่คุณต้องการบรรลุคือขนมที่มีไวยากรณ์เช่นobj.bla = 5แทนที่จะเป็นobj['bla'] = 5โดยเฉพาะอย่างยิ่งถ้าคุณต้องทำซ้ำมาก ๆ คุณอาจต้องการใช้คลาสคอนเทนเนอร์ธรรมดาตามคำแนะนำของมาร์ติเนาส์ อย่างไรก็ตามรหัสมีค่อนข้างป่องและช้าโดยไม่จำเป็น คุณสามารถทำให้มันเรียบง่ายเช่นนั้น:

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

อีกเหตุผลหนึ่งในการเปลี่ยนไปใช้namedtuples หรือ class __slots__อาจเป็นการใช้หน่วยความจำ Dicts ต้องการหน่วยความจำมากกว่าประเภทรายการอย่างมากดังนั้นนี่อาจเป็นประเด็นที่น่าคิด

อย่างไรก็ตามในกรณีเฉพาะของคุณดูเหมือนจะไม่มีแรงจูงใจใด ๆ ที่จะเปลี่ยนจากการใช้งานปัจจุบันของคุณ ดูเหมือนคุณจะไม่ได้ดูแลรักษาวัตถุเหล่านี้เป็นล้าน ๆ ชิ้นดังนั้นจึงไม่จำเป็นต้องใช้ประเภทรายการที่ได้รับ และเป็นจริงมีบางตรรกะการทำงานภายในเพื่อให้คุณยังไม่ควรมีด้วย__init__AttrDict


types.SimpleNamespace(พร้อมใช้งานตั้งแต่ Python 3.3) เป็นอีกทางเลือกหนึ่งของ AttrDict ที่กำหนดเอง
Cristian Ciupitu

4

อาจเป็นไปได้ที่จะมีเค้กของคุณและกินมันด้วย กล่าวอีกนัยหนึ่งคือคุณสามารถสร้างสิ่งที่ให้ฟังก์ชันการทำงานของทั้งคลาสและอินสแตนซ์พจนานุกรม ดูสูตรDɪᴄᴛɪᴏɴᴀʀʏᴡɪᴛʜᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ss ของ ActiveStateและความคิดเห็นเกี่ยวกับวิธีการทำเช่นนั้น

ถ้าคุณตัดสินใจที่จะใช้ระดับปกติมากกว่า subclass ผมได้พบTʜᴇsɪᴍᴘʟᴇʙᴜᴛʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀᴏғᴀʙᴜɴᴄʜᴏғɴᴀᴍᴇᴅsᴛᴜғғ" ᴄʟᴀssสูตร (โดยอเล็กซ์เทล) ที่จะมีมากมีความยืดหยุ่นและมีประโยชน์สำหรับการเรียงลำดับของสิ่งนั้น ดูเหมือนว่าคุณกำลังทำอยู่ (เช่นสร้างตัวรวบรวมข้อมูลแบบง่ายสัมพัทธ์) เนื่องจากเป็นคลาสคุณจึงสามารถขยายฟังก์ชันการทำงานเพิ่มเติมได้อย่างง่ายดายโดยการเพิ่มวิธีการ

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

อัปเดต

คลาสobject(ซึ่งไม่มี__dict__) ชื่อคลาสย่อยSimpleNamespace(ซึ่งมีอยู่) ถูกเพิ่มเข้าไปในtypesโมดูล Python 3.3 และยังเป็นอีกทางเลือกหนึ่ง


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