ตัวแปรอินสแตนซ์เทียบกับตัวแปรคลาสใน Python


121

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

ตัวแปรคลาส:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

ตัวแปรอินสแตนซ์:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass

4
หลังจากอ่านคำถามนี้และเห็นคำตอบหนึ่งในคำถามแรกของฉันคือ "แล้วฉันจะเข้าถึงตัวแปรของคลาสได้อย่างไร" - นั่นเป็นเพราะถึงจุดนี้ฉันใช้ตัวแปรอินสแตนซ์เท่านั้น เพื่อตอบคำถามของฉันเองคุณทำผ่านชื่อคลาสเองแม้ว่าในทางเทคนิคคุณสามารถทำได้ผ่านอินสแตนซ์เช่นกัน นี่คือลิงค์สำหรับอ่านสำหรับคนอื่นที่มีคำถามเดียวกัน: stackoverflow.com/a/3434596/4561887
Gabriel Staples

คำตอบ:


159

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


7
ไม่เคยได้ยินรูปแบบ Borg? การมีอินสแตนซ์เพียงครั้งเดียวเป็นวิธีที่ผิดในตอนแรก
Devin Jeanpierre

435
@ เดวินใช่ฉันเคยได้ยินเกี่ยวกับรูปแบบ Borg เนื่องจากฉันเป็นคนที่แนะนำมัน (ในปี 2001 cfr code.activestate.com/recipes/… ;-) แต่ไม่มีอะไรผิดปกติในกรณีง่ายๆเพียงแค่มีอินสแตนซ์เดียวโดยไม่มีการบังคับใช้
Alex Martelli

2
@ user1767754 ง่ายต่อการสร้างด้วยตัวเองpython -mtimeit- แต่เพิ่งทำใน python3.4 ฉันทราบว่าการเข้าถึงintตัวแปรคลาสนั้นเร็วกว่าตัวแปรอินสแตนซ์ประมาณ 5 ถึง 11 นาโนวินาทีในเวิร์กสเตชันเก่าของฉัน - ไม่แน่ใจว่าอะไร codepath ทำให้เป็นเช่นนั้น
Alex Martelli

45

สะท้อนคำแนะนำของ MikeและAlex เพิ่มเติมและเพิ่มสีของตัวเอง ...

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

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

  1. ชาวบ้าน
  2. nonlocals
  3. Globals
  4. สร้างอิน

สำหรับการเข้าถึงแอตทริบิวต์ลำดับคือ:

  1. ตัวอย่าง
  2. ชั้น
  3. คลาสพื้นฐานตามที่กำหนดโดยMRO (ลำดับวิธีการแก้ปัญหา)

เทคนิคทั้งสองทำงานในลักษณะ "ภายใน - ภายนอก" ซึ่งหมายความว่าจะมีการตรวจสอบวัตถุในพื้นที่มากที่สุดก่อนจากนั้นจึงตรวจสอบชั้นนอกตามลำดับ

ในตัวอย่างของคุณด้านบนสมมติว่าคุณกำลังค้นหาpathแอตทริบิวต์ เมื่อพบการอ้างอิงเช่น " self.path" Python จะดูแอตทริบิวต์อินสแตนซ์ก่อนสำหรับการจับคู่ เมื่อล้มเหลวจะตรวจสอบคลาสที่อ็อบเจ็กต์ถูกสร้างอินสแตนซ์ สุดท้ายมันจะค้นหาคลาสพื้นฐาน ดังที่อเล็กซ์กล่าวไว้หากพบแอตทริบิวต์ของคุณในอินสแตนซ์ก็ไม่จำเป็นต้องมองหาที่อื่นดังนั้นคุณจึงประหยัดเวลาได้เล็กน้อย

อย่างไรก็ตามหากคุณยืนยันในแอตทริบิวต์คลาสคุณต้องมีการค้นหาเพิ่มเติม หรือทางเลือกอื่น ๆ ของคุณคือการอ้างถึงวัตถุที่ผ่านการเรียนแทนตัวอย่างเช่นแทนMyController.path self.pathนั่นคือการค้นหาโดยตรงซึ่งจะได้รับการค้นหาที่รอการตัดบัญชี แต่ตามที่ alex กล่าวถึงด้านล่างนี้เป็นตัวแปรส่วนกลางดังนั้นคุณจึงสูญเสียบิตที่คุณคิดว่าจะบันทึก (เว้นแต่คุณจะสร้างการอ้างอิงในท้องถิ่นไปยังชื่อคลาส [global] )

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


@wescpy แต่MyControllerถูกค้นหาใน globals ดังนั้นต้นทุนรวมจึงสูงกว่าself.pathที่pathตัวแปรอินสแตนซ์ (เนื่องจากselfเป็นแบบโลคัลสำหรับ method == super-fast lookup)
Alex Martelli

อาจริง จับดี. ฉันเดาว่าวิธีแก้ปัญหาเพียงอย่างเดียวคือการสร้างข้อมูลอ้างอิงในท้องถิ่น ... ณ จุดนี้มันไม่คุ้มค่าจริงๆ
wescpy

24

หากมีข้อสงสัยคุณอาจต้องการแอตทริบิวต์อินสแตนซ์

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


1
ตัวแปรคลาสเป็นค่าคงที่อ่านอย่างเดียว ถ้า Python ให้ฉันกำหนดค่าคงที่ฉันจะเขียนมันเป็นค่าคงที่
deamon

1
@deamon ฉันมีแนวโน้มที่จะใส่ค่าคงที่อยู่นอกนิยามคลาสและตั้งชื่อเป็นตัวพิมพ์ใหญ่ทั้งหมด การใส่ไว้ในชั้นเรียนก็ทำได้เช่นกัน การสร้างแอตทริบิวต์อินสแตนซ์จะไม่ส่งผลเสียอะไรเลย แต่อาจจะแปลกไปหน่อย ฉันไม่คิดว่านี่เป็นปัญหาที่ชุมชนอยู่เบื้องหลังตัวเลือกใดตัวเลือกหนึ่งมากเกินไป
Mike Graham

@MikeGraham FWIW, Python Style Guide ของ Googleแนะนำให้หลีกเลี่ยงตัวแปรส่วนกลางที่สนับสนุนตัวแปรคลาส มีข้อยกเว้นแม้ว่า
Dennis

นี่คือการเชื่อมโยงใหม่เพื่อคู่มือสไตล์ของ Google หลาม ตอนนี้มีการเขียนไว้อย่างเรียบง่าย: avoid global variablesและคำจำกัดความของมันคือตัวแปรส่วนกลางยังเป็นตัวแปรที่ประกาศเป็นคุณสมบัติของคลาส อย่างไรก็ตามคู่มือสไตล์ของ Python ( PEP-8 ) ควรเป็นที่แรกสำหรับคำถามประเภทนี้ จากนั้นความคิดของคุณเองก็ควรเป็นเครื่องมือในการเลือก (แน่นอนว่าคุณสามารถรับแนวคิดจาก Google ได้เช่นกัน)
colidyre

4

คำถามเดียวกันกับประสิทธิภาพการเข้าถึงตัวแปรคลาสใน Python - โค้ดที่นี่ดัดแปลงมาจาก @Edward Loper

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

มี 4 ขอบเขตที่คุณสามารถเข้าถึงตัวแปรจาก:

  1. ตัวแปรอินสแตนซ์ (self.varname)
  2. ตัวแปรคลาส (Classname.varname)
  3. ตัวแปรโมดูล (VARNAME)
  4. ตัวแปรท้องถิ่น (varname)

การทดสอบ:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

ผลลัพธ์:

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