ความแตกต่างระหว่าง __getattr__ กับ __getattribute__


418

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



5
@Trilarion ยังกล่าวว่าคำถามคำตอบจากที่นี่ ...
JC Rocamonde

คำตอบ:


497

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

__getattribute__ถูกเรียกใช้ก่อนที่จะดูแอตทริบิวต์ที่แท้จริงของวัตถุและอาจมีความยุ่งยากในการใช้อย่างถูกต้อง คุณสามารถจบด้วยการเรียกซ้ำแบบไม่สิ้นสุดได้อย่างง่ายดายมาก

คลาสแบบใหม่ที่ได้รับมาจากคลาสแบบobjectเก่านั้นเป็น Python 2.x โดยไม่มีคลาสพื้นฐานที่ชัดเจน แต่ความแตกต่างระหว่างแบบเก่าและการเรียนแบบใหม่ไม่ได้เป็นหนึ่งที่สำคัญเมื่อมีการเลือกระหว่างและ__getattr____getattribute__

__getattr__คุณเกือบจะแน่นอนต้องการ


6
ฉันสามารถนำทั้งสองอย่างมาใช้ในชั้นเรียนเดียวกันได้หรือไม่? หากฉันสามารถนำไปใช้กับทั้งสองสิ่งได้
Alcott

13
@Alcott: คุณสามารถใช้ทั้งคู่ได้ แต่ฉันไม่แน่ใจว่าทำไมคุณถึงต้องการ __getattribute__จะได้รับการเรียกร้องให้ทุกคนเข้าถึงและ__getattr__จะได้รับการเรียกร้องให้มีครั้งที่ยก__getattribute__ AttributeErrorทำไมไม่เก็บทุกอย่างไว้ในที่เดียว?
Ned Batchelder

10
@NedBatchelder ถ้าคุณต้องการที่จะ (ตามเงื่อนไข) __getattribute__แทนที่การโทรไปยังวิธีการที่มีอยู่ที่คุณต้องการจะใช้
Jace Browning

28
"เพื่อหลีกเลี่ยงการเรียกซ้ำไม่สิ้นสุดในวิธีนี้การใช้งานควรเรียกเมธอดคลาสพื้นฐานที่มีชื่อเดียวกันเพื่อเข้าถึงแอททริบิวที่ต้องการเช่นวัตถุ .__ getattribute __ (ตัวเองชื่อ)"
kmonsoor

1
@kmonsoor นั่นเป็นเหตุผลที่ดีที่จะใช้ทั้งสองอย่าง objec.__getattribute__เรียกใช้myclass.__getattr__ภายใต้สถานการณ์ที่เหมาะสม
นักฟิสิกส์บ้า

138

ให้ดูตัวอย่างง่ายๆของวิธีการทั้งสอง__getattr__และ__getattribute__เวทมนตร์

__getattr__

Python จะเรียก __getattr__ใช้เมธอดทุกครั้งที่คุณร้องขอแอตทริบิวต์ที่ยังไม่ได้กำหนดไว้ ในตัวอย่างต่อไปนี้การนับชั้นเรียนของฉันไม่มี__getattr__วิธี ตอนนี้เป็นหลักเมื่อฉันพยายามเข้าถึงทั้งสองobj1.myminและobj1.mymaxคุณลักษณะทุกอย่างทำงานได้ดี แต่เมื่อฉันพยายามเข้าถึงobj1.mycurrentattribute - Python ให้ฉันAttributeError: 'Count' object has no attribute 'mycurrent'

class Count():
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent)  --> AttributeError: 'Count' object has no attribute 'mycurrent'

ตอนนี้ชั้นเรียนของฉันจำนวนมี__getattr__วิธีการ ตอนนี้เมื่อฉันพยายามเข้าถึง obj1.mycurrentแอ็ตทริบิวต์ - python จะส่งคืนสิ่งที่ฉันได้นำไปใช้ใน__getattr__วิธีการของฉัน ในตัวอย่างของฉันเมื่อใดก็ตามที่ฉันพยายามเรียกแอตทริบิวต์ที่ไม่มีอยู่ python สร้างคุณลักษณะนั้นและตั้งค่าเป็นค่าจำนวนเต็ม 0

class Count:
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax    

    def __getattr__(self, item):
        self.__dict__[item]=0
        return 0

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)

__getattribute__

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

เมื่อใดก็ตามที่มีคนพยายามเข้าถึงคุณลักษณะของฉันที่เริ่มต้นด้วยpython 'สตริง' substring ทำให้เกิดAttributeErrorข้อยกเว้น มิฉะนั้นจะส่งคืนแอตทริบิวต์นั้น

class Count:

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item) 
        # or you can use ---return super().__getattribute__(item)

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

สำคัญ: เพื่อหลีกเลี่ยงการเรียกซ้ำแบบไม่สิ้นสุดใน__getattribute__เมธอดการประยุกต์ใช้ควรเรียกเมธอดคลาสพื้นฐานที่มีชื่อเดียวกันเพื่อเข้าถึงแอ็ตทริบิวต์ที่ต้องการ ตัวอย่างเช่น: object.__getattribute__(self, name)หรือ super().__getattribute__(item)ไม่self.__dict__[item]

สิ่งสำคัญ

หากคลาสของคุณมีทั้งวิธี getattrและgetattributeวิธีการแบบเวทย์มนตร์นั้น __getattribute__จะถูกเรียกก่อน แต่ถ้า __getattribute__เพิ่ม AttributeErrorข้อยกเว้นข้อยกเว้นจะถูกละเว้นและ__getattr__วิธีการจะถูกเรียกใช้ ดูตัวอย่างต่อไปนี้:

class Count(object):

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattr__(self, item):
            self.__dict__[item]=0
            return 0

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item)
        # or you can use ---return super().__getattribute__(item)
        # note this class subclass object

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

1
ฉันไม่แน่ใจว่าจะใช้กรณีใดในการเอาชนะ__getattribute__แต่สิ่งนี้ไม่แน่นอน เพราะตามตัวอย่างของคุณสิ่งที่คุณกำลังทำอยู่__getattribute__คือการยกAttributeErrorข้อยกเว้นหากแอตทริบิวต์นั้นไม่มีอยู่ใน__dict__วัตถุ แต่คุณไม่จำเป็นต้องใช้สิ่งนี้เพราะนี่เป็นการใช้งานเริ่มต้น__getattribute__และ infact __getattr__นั้นเป็นสิ่งที่คุณต้องการในฐานะกลไกทางเลือก
Rohit

@Rohit currentถูกกำหนดในกรณีของCount(ดู__init__) ดังนั้นเพียงแค่เพิ่มAttributeErrorถ้าแอตทริบิวต์ที่ไม่ได้มีไม่มากสิ่งที่เกิดขึ้น - มันคล้อยตามไป__getattr__สำหรับทุกชื่อขึ้นต้น 'เลว' รวมถึงcurrentแต่ยังcurious, curly...
joelb

16

นี่เป็นเพียงตัวอย่างขึ้นอยู่กับคำอธิบายของเน็ด Batchelder

__getattr__ ตัวอย่าง:

class Foo(object):
    def __getattr__(self, attr):
        print "looking up", attr
        value = 42
        self.__dict__[attr] = value
        return value

f = Foo()
print f.x 
#output >>> looking up x 42

f.x = 3
print f.x 
#output >>> 3

print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')

และถ้าใช้ตัวอย่างเดียวกันกับ__getattribute__คุณก็จะได้ >>>RuntimeError: maximum recursion depth exceeded while calling a Python object


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

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

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

1
@Simon K Bhatta4ya คำสั่งพิมพ์ครั้งสุดท้ายของคุณคือความคิดเห็น ขวา? มันเป็นเส้นที่ยาวและน่าเบื่อที่จะอ่าน (ต้องเลื่อนไปทางด้านขวามาก ๆ ) สิ่งที่เกี่ยวกับการวางสายหลังส่วนรหัส? หรือถ้าคุณต้องการวางไว้ในส่วนของรหัสฉันคิดว่ามันจะเป็นการดีกว่าถ้าแบ่งมันออกเป็นสองบรรทัด
Md. Abu Nafee Ibna Zahid

14

คลาสสไตล์ใหม่สืบทอดจากobjectหรือคลาสสไตล์ใหม่อื่น:

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

ชั้นเรียนแบบเก่าไม่:

class SomeObject:
    pass

สิ่งนี้ใช้ได้กับ Python 2 เท่านั้น - ใน Python 3 ทั้งหมดข้างต้นจะสร้างคลาสสไตล์ใหม่

ดูที่9. Classes (Python tutorial), NewClassVsClassicClassและอะไรคือความแตกต่างระหว่างสไตล์เก่ากับคลาสสไตล์ใหม่ใน Python? สำหรับรายละเอียด


6

คลาสสไตล์ใหม่เป็นคลาสย่อยที่ "object" (โดยตรงหรือโดยอ้อม) พวกเขามี__new__วิธีการเรียนเพิ่มเติม__init__และมีพฤติกรรมระดับต่ำเหตุผลมากกว่า

โดยปกติแล้วคุณจะต้องการแทนที่__getattr__(ถ้าคุณกำลังเอาชนะ) ไม่เช่นนั้นคุณจะมีปัญหาในการรองรับไวยากรณ์ "self.foo" ภายในวิธีการของคุณ

ข้อมูลเพิ่มเติม: http://www.devx.com/opensource/Article/31482/0/page/4


2

ในการอ่านผ่าน Beazley & Jones PCB ฉันได้สะดุดกับกรณีการใช้งานที่ชัดเจนและใช้งานได้จริงเพื่อ__getattr__ช่วยตอบ "ตอน" ส่วนหนึ่งของคำถามของ OP จากหนังสือ:

" __getattr__()วิธีการดังกล่าวเป็นสิ่งที่จับได้สำหรับการค้นหาแอททริบิวต์มันเป็นวิธีการที่เรียกว่าหากรหัสพยายามเข้าถึงแอททริบิวที่ไม่มีอยู่จริง" เรารู้เรื่องนี้จากคำตอบข้างต้น แต่ในสูตร PCB 8.15, ฟังก์ชั่นนี้จะใช้ในการใช้รูปแบบการออกแบบคณะผู้แทน ถ้า Object A มีคุณสมบัติ Object B ที่ใช้วิธีการหลายอย่างที่ Object A ต้องการมอบหมายแทนการกำหนดวิธีการทั้งหมดของ Object B ใน Object A ใหม่เพื่อเรียกเมธอดของ Object B ให้นิยาม__getattr__()วิธีดังนี้:

def __getattr__(self, name):
    return getattr(self._b, name)

โดยที่ _b เป็นชื่อของแอททริบิวของ Object A ที่เป็น Object B เมื่อเมธอดที่กำหนดไว้บน Object B ถูกเรียกบน Object A __getattr__เมธอดจะถูกเรียกใช้ที่ส่วนท้ายของเชนการค้นหา สิ่งนี้จะทำให้โค้ดสะอาดขึ้นเช่นกันเนื่องจากคุณไม่มีรายการวิธีที่กำหนดไว้สำหรับการมอบหมายให้วัตถุอื่นเท่านั้น

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