ฉันจะติดตั้ง __getattribute__ โดยไม่มีข้อผิดพลาดซ้ำไม่สิ้นสุดได้อย่างไร


104

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

ฉันลองทำสิ่งต่อไปนี้ (ซึ่งควรแสดงให้เห็นถึงสิ่งที่ฉันพยายามทำ) แต่ฉันได้รับข้อผิดพลาดในการเรียกซ้ำ

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name=='test':
            return 0.
        else:
            return self.__dict__[name]

>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp

คำตอบ:


130

คุณได้รับข้อผิดพลาดในการเรียกซ้ำเนื่องจากความพยายามของคุณในการเข้าถึงself.__dict__แอตทริบิวต์ภายใน__getattribute__เรียกคุณ__getattribute__อีกครั้ง ถ้าคุณใช้object's __getattribute__แทนการทำงาน:

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name=='test':
            return 0.
        else:
            return object.__getattribute__(self, name)

ใช้งานได้เนื่องจากobject(ในตัวอย่างนี้) เป็นคลาสพื้นฐาน โดยการเรียกรุ่นพื้นฐานของ__getattribute__คุณจะหลีกเลี่ยงนรกที่เกิดซ้ำที่คุณเคยอยู่มาก่อน

เอาต์พุต Ipython พร้อมรหัสใน foo.py:

In [1]: from foo import *

In [2]: d = D()

In [3]: d.test
Out[3]: 0.0

In [4]: d.test2
Out[4]: 21

อัปเดต:

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


1
น่าสนใจ. คุณกำลังทำอะไรอยู่ที่นั่น? ทำไมออบเจ็กต์ถึงมีตัวแปรของฉัน
Greg

1
.. และฉันต้องการใช้วัตถุเสมอหรือไม่? จะเกิดอะไรขึ้นถ้าฉันได้รับมรดกจากคลาสอื่น ๆ ?
Greg

1
ใช่ทุกครั้งที่คุณสร้างคลาสและคุณไม่ได้เขียนของคุณเองคุณจะใช้getattribute ที่จัดทำโดย object
Egil

20
ไม่ดีกว่าที่จะใช้ super () ดังนั้นจึงใช้เมธอดgetattributeแรกที่python พบในคลาสพื้นฐานของคุณ? -super(D, self).__getattribute__(name)
gepatino

10
หรือคุณสามารถใช้super().__getattribute__(name)ใน Python 3
jeromej

26

อันที่จริงฉันเชื่อว่าคุณต้องการใช้ไฟล์ __getattr__วิธีพิเศษแทน

อ้างจากเอกสาร Python:

__getattr__( self, name)

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

หมายเหตุ: เพื่อให้ใช้งานได้อินสแตนซ์ไม่ควรมีtestแอตทริบิวต์ดังนั้นจึงself.test=20ควรลบบรรทัดออก


2
อันที่จริงตามธรรมชาติของรหัสของ OP ที่เอาชนะ__getattr__สำหรับtestจะไร้ประโยชน์เพราะมันมักจะพบว่ามัน "ในสถานที่ตามปกติ"
Casey Kuball

1
ลิงก์ปัจจุบันไปยังเอกสาร Python ที่เกี่ยวข้อง (ดูเหมือนจะแตกต่างจากที่อ้างถึงในคำตอบ): docs.python.org/3/reference/datamodel.html#object.__getattr__
CrepeGoat

18

การอ้างอิงภาษา Python:

เพื่อหลีกเลี่ยงการเรียกซ้ำอนันต์ในวิธีการนี้การดำเนินงานของควรเรียกวิธีการชั้นฐานที่มีชื่อเดียวกันในการเข้าถึงใด ๆ object.__getattribute__(self, name)แอตทริบิวต์จะต้องยกตัวอย่างเช่น

ความหมาย:

def __getattribute__(self,name):
    ...
        return self.__dict__[name]

__dict__คุณกำลังเรียกร้องให้มีแอตทริบิวต์ที่เรียกว่า เนื่องจากเป็นแอตทริบิวต์จึง__getattribute__ถูกเรียกในการค้นหา__dict__ว่าสาย__getattribute__ที่เรียก ... yada yada yada

return  object.__getattribute__(self, name)

การใช้คลาสพื้นฐาน__getattribute__ช่วยในการค้นหาแอตทริบิวต์ที่แท้จริง


13

คุณแน่ใจว่าคุณต้องการที่จะใช้__getattribute__? คุณกำลังพยายามทำอะไรอยู่?

วิธีที่ง่ายที่สุดในการทำสิ่งที่คุณขอคือ:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    test = 0

หรือ:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    @property
    def test(self):
        return 0

แก้ไข: โปรดทราบว่าอินสแตนซ์ของDจะมีค่าที่แตกต่างกันtestในแต่ละกรณี ในกรณีแรกd.testจะเป็น 20 ในกรณีที่สองจะเป็น 0 ฉันจะปล่อยให้คุณหาสาเหตุ

Edit2: Greg ชี้ให้เห็นว่าตัวอย่างที่ 2 จะล้มเหลวเนื่องจากคุณสมบัติเป็นแบบอ่านอย่างเดียวและ__init__เมธอดพยายามตั้งค่าเป็น 20 ตัวอย่างที่สมบูรณ์ยิ่งขึ้นสำหรับสิ่งนั้นคือ:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21

    _test = 0

    def get_test(self):
        return self._test

    def set_test(self, value):
        self._test = value

    test = property(get_test, set_test)

เห็นได้ชัดว่าในชั้นเรียนนี้แทบจะไม่มีประโยชน์เลย แต่มันทำให้คุณมีความคิดที่จะก้าวต่อไป


โอ้นั่นไม่ได้ผลเมื่อคุณเรียกใช้ชั้นเรียนไม่ใช่เหรอ? ไฟล์ "Script1.py" บรรทัดที่ 5 ในinit self.test = 20 AttributeError: ไม่สามารถตั้งค่าแอตทริบิวต์
Greg

จริง. ฉันจะแก้ไขเป็นตัวอย่างที่สาม เห็นดี
Singletoned

6

วิธี__getattribute__การใช้คืออะไร?

เรียกว่าก่อนการค้นหาแบบจุดปกติ ถ้ามันขึ้นAttributeErrorเราก็โทร__getattr__.

การใช้วิธีนี้ค่อนข้างหายาก มีเพียงสองคำจำกัดความในไลบรารีมาตรฐาน:

$ grep -Erl  "def __getattribute__\(self" cpython/Lib | grep -v "/test/"
cpython/Lib/_threading_local.py
cpython/Lib/importlib/util.py

ปฏิบัติที่ดีที่สุด

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

class D(object):
    def __init__(self):
        self.test2=21

    @property
    def test(self):
        return 0.

    @test.setter
    def test(self, value):
        '''dummy function to avoid AttributeError on setting property'''

    @test.deleter
    def test(self):
        '''dummy function to avoid AttributeError on deleting property'''

และการใช้งาน:

>>> o = D()
>>> o.test
0.0
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0

คุณสมบัติเป็นตัวบอกข้อมูลดังนั้นจึงเป็นสิ่งแรกที่ค้นหาในอัลกอริทึมการค้นหาแบบจุดปกติ

ตัวเลือกสำหรับ __getattribute__

คุณมีหลายทางเลือกหากคุณจำเป็นต้องใช้การค้นหาทุกแอตทริบิวต์ผ่านทาง__getattribute__.

  • เพิ่มขึ้นAttributeErrorทำให้__getattr__ถูกเรียก (หากมีการใช้งาน)
  • คืนบางสิ่งบางอย่างจากมันโดย
    • ใช้superเพื่อเรียกการติดตั้งพาเรนต์ (อาจจะobjectเป็น)
    • โทร __getattr__
    • ใช้อัลกอริทึมการค้นหาแบบจุดของคุณเอง

ตัวอย่างเช่น:

class NoisyAttributes(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self, name):
        print('getting: ' + name)
        try:
            return super(NoisyAttributes, self).__getattribute__(name)
        except AttributeError:
            print('oh no, AttributeError caught and reraising')
            raise
    def __getattr__(self, name):
        """Called if __getattribute__ raises AttributeError"""
        return 'close but no ' + name    


>>> n = NoisyAttributes()
>>> nfoo = n.foo
getting: foo
oh no, AttributeError caught and reraising
>>> nfoo
'close but no foo'
>>> n.test
getting: test
20

สิ่งที่คุณต้องการในตอนแรก

และตัวอย่างนี้แสดงให้เห็นว่าคุณจะทำสิ่งที่คุณต้องการในตอนแรกได้อย่างไร:

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name=='test':
            return 0.
        else:
            return super(D, self).__getattribute__(name)

และจะประพฤติเช่นนี้:

>>> o = D()
>>> o.test = 'foo'
>>> o.test
0.0
>>> del o.test
>>> o.test
0.0
>>> del o.test

Traceback (most recent call last):
  File "<pyshell#216>", line 1, in <module>
    del o.test
AttributeError: test

การตรวจสอบโค้ด

รหัสของคุณพร้อมความคิดเห็น __getattribute__คุณมีการค้นหาประตนเองใน นี่คือสาเหตุที่คุณได้รับข้อผิดพลาดในการเรียกซ้ำ คุณสามารถตรวจสอบว่าชื่อ"__dict__"และการใช้งานsuperในการแก้ปัญหา __slots__แต่ที่ไม่ครอบคลุม ฉันจะปล่อยให้มันเป็นแบบฝึกหัดสำหรับผู้อ่าน

class D(object):
    def __init__(self):
        self.test=20
        self.test2=21
    def __getattribute__(self,name):
        if name=='test':
            return 0.
        else:      #   v--- Dotted lookup on self in __getattribute__
            return self.__dict__[name]

>>> print D().test
0.0
>>> print D().test2
...
RuntimeError: maximum recursion depth exceeded in cmp

5

นี่คือเวอร์ชันที่เชื่อถือได้มากขึ้น:

class D(object):
    def __init__(self):
        self.test = 20
        self.test2 = 21
    def __getattribute__(self, name):
        if name == 'test':
            return 0.
        else:
            return super(D, self).__getattribute__(name)

มันโทร เมธอด __ getattribute __จากคลาสพาเรนต์ในที่สุดก็ถอยกลับไปที่อ็อบเจ็กต์ __ เมธอด getattribute __ถ้าบรรพบุรุษอื่นไม่ลบล้างมัน

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