3

__getattr__魔法のメソッドとを使用してサークル クラスを作成しようとしていますが、機能__setattr__しているように見えますが、__getattr__実装すると(値が int の場合にと__setattr__の値のみを設定し、 whenを発生させる必要があります)。ユーザーが属性、、およびをに設定しようとすると、最大の再帰エラーがスローされます。コメントアウトすると、then は正常に機能します。xyAttributeErrorareacircumferencedistancecircle__getattr____getattr__

from math import pi, hypot, sqrt
'''
Circle class using __getattr__, and __setattr__ (rename circle2)
'''


# __getattr__(self, name): Automatically called when the attribute name
#       is accessed and the object has no such attribute.
# __setattr__(self, name, value): Automatically called when an attempt is made to bind the attribute name to value.

class Circle:
    def __init__(self, x, y, r):
        self.x = x
        self.y = y
        self.r = r
        self.area = pi * self.r * self.r
        self.circumference = 2 * pi * self.r
        self.distance_to_origin = abs(sqrt((self.x - 0)*(self.x - 0) + (self.y - 0) * (self.y - 0)) - self.r)

    def __getattr__(self, name):
        if name in ["x", "y", "r", "area", "circumference", "distance_to_origin"]:
            print('__get if statement') # check getattr working
            return getattr(self, name)
        else:
            print('Not an attribute')
            return None
    '''
    def __setattr__(self, name, value):
        print(name, value)
        if name in ['x', 'y']:
            if isinstance(value, int):
                print('we can set x,y')
                self.__dict__[name] = value
            else:  # value isn't an int
                raise TypeError('Expected an int')
        elif name in ['area', 'circumference', 'distance_to_origin']:
            raise RuntimeError('Cannot set attribute')
    '''

if __name__ == '__main__':

    circle = Circle(x=3, y=4, r=5)
    # print(circle.x)
    print(circle.__getattr__('x'))
    # print(circle.y)
    print(circle.__getattr__('y'))
    # print(circle.r)
    print(circle.__getattr__('r'))
    # print(circle.area)
    print(circle.__getattr__('area'))
    # print(circle.circumference)
    print(circle.__getattr__('circumference'))
    # print(circle.distance_to_origin)
    print(circle.__getattr__('distance_to_origin'))
    # print(circle.test)
    '''
    tests = [('circle.x = 12.3', "print('Setting circle.x to non-integer fails')"),
             ('circle.y = 23.4', "print('Setting circle.y to non-integer fails')"),
             ('circle.area = 23.4', "print('Setting circle.area fails')"),
             ('circle.circumference = 23.4', "print('Setting circle.circumference fails')"),
             ('circle.distance_to_origin = 23.4', "print('Setting circle.distance_to_origin fails')"),
             ('circle.z = 5.6', "print('Setting circle.z fails')"),
             ('print(circle.z)', "print('Printing circle.z fails')")]
    for test in tests:
        try:
            exec(test[0])
        except:
            exec(test[1])
    '''

__setattr__コメントアウトすると、テスト コードは次のようになります。

if __name__ == '__main__':

    circle = Circle(x=3, y=4, r=5)
    # print(circle.x)
    print(circle.__getattr__('x'))
    # print(circle.y)
    print(circle.__getattr__('y'))
    # print(circle.r)
    print(circle.__getattr__('r'))
    # print(circle.area)
    print(circle.__getattr__('area'))
    # print(circle.circumference)
    print(circle.__getattr__('circumference'))
    # print(circle.distance_to_origin)
    print(circle.__getattr__('distance_to_origin'))

プリントアウト:

__get if statement
3
__get if statement
4
__get if statement
5
__get if statement
78.53981633974483
__get if statement
31.41592653589793
__get if statement
0.0
4

2 に答える 2

1

改善されたソリューション

ここでの議論に基づいて、これは短く改良されたバージョンです。元のソリューションと同じことを達成します。

from math import pi, hypot, sqrt


class Circle:
    def __init__(self, x, y, r):
        self.x = x
        self.y = y
        super().__setattr__('r', r)
        super().__setattr__('area', pi * self.r * self.r)
        super().__setattr__('circumference', 2 * pi * self.r)
        super().__setattr__('distance_to_origin',
                            abs(sqrt(self.x * self.x + self.y * self.y) - self.r))

    def __setattr__(self, name, value):
        if name in ['x', 'y']:
            if isinstance(value, int):
                print('we can set x,y')
                super().__setattr__(name, value)
            else:  # value isn't an int
                raise TypeError('Expected an int for: {}'.format(name))
        else:
            raise AttributeError('Cannot set attribute: {}'.format(name))

解決

すべて一緒に回避__getattr__()し、フラグを使用して、既に実行されているself._intializedかどうかを通知します。__init__()

from math import pi, hypot, sqrt
'''
Circle class using __getattr__, and __setattr__ (rename circle2)
'''


# __getattr__(self, name): Automatically called when the attribute name
#       is accessed and the object has no such attribute.
# __setattr__(self, name, value): Automatically called when an attempt is made to bind the attribute name to value.

class Circle:
    def __init__(self, x, y, r):
        self._intialized = False
        self.x = x
        self.y = y
        self.r = r
        self.area = pi * self.r * self.r
        self.circumference = 2 * pi * self.r
        self.distance_to_origin = abs(sqrt(self.x * self.x + self.y * self.y) - self.r)
        self._intialized = True


    def __setattr__(self, name, value):
        if name in ['_intialized']:
            self.__dict__[name] = value
            return
        if name in ['x', 'y']:
            if isinstance(value, int):
                print('we can set x,y')
                self.__dict__[name] = value
            else:  # value isn't an int
                raise TypeError('Expected an int for: {}'.format(name))
        elif not self._intialized:
            self.__dict__[name] = value

        elif name in ['area', 'circumference', 'distance_to_origin']:
            raise AttributeError('Cannot set attribute: {}'.format(name))

if __name__ == '__main__':

    circle = Circle(x=3, y=4, r=5)
    print('x:', circle.x)
    print('y:', circle.y)
    print('r:', circle.r)
    print('area:', circle.area)
    print('circumference:', circle.circumference)
    print('distance_to_origin:', circle.distance_to_origin)
    tests = [('circle.x = 12.3', "print('Setting circle.x to non-integer fails')"),
             ('circle.y = 23.4', "print('Setting circle.y to non-integer fails')"),
             ('circle.area = 23.4', "print('Setting circle.area fails')"),
             ('circle.circumference = 23.4', "print('Setting circle.circumference fails')"),
             ('circle.distance_to_origin = 23.4', "print('Setting circle.distance_to_origin fails')"),
             ('circle.z = 5.6', "print('Setting circle.z fails')"),
             ('print(circle.z)', "print('Printing circle.z fails')")]
    for test in tests:
        try:
            exec(test[0])
        except:
            exec(test[1])

出力は良さそうです:

python get_set_attr.py 
we can set x,y
we can set x,y
x: 3
y: 4
r: 5
area: 78.53981633974483
circumference: 31.41592653589793
distance_to_origin: 0.0
Setting circle.x to non-integer fails
Setting circle.y to non-integer fails
Setting circle.area fails
Setting circle.circumference fails
Setting circle.distance_to_origin fails
Printing circle.z fails

変化

これにより、他の名前の属性を設定できます。

circle.xyz = 100

しかし、それはありません:

circle.xyz


Traceback (most recent call last):
  File "get_set_attr.py", line 62, in <module>
    circle.xyz
 AttributeError: 'Circle' object has no attribute 'xyz'

のこの実装はこれ__setattr__を回避します:

def __setattr__(self, name, value):
    if name in ['_intialized']:
        self.__dict__[name] = value
        return
    if name in ['x', 'y']:
        if isinstance(value, int):
            print('we can set x,y')
            self.__dict__[name] = value
            return
        else:  # value isn't an int
            raise TypeError('Expected an int for: {}'.format(name))
    elif not self._intialized:
        self.__dict__[name] = value
    else:
        raise AttributeError('Cannot set attribute: {}'.format(name))

いつ使う__getattr__()

存在しない属性にアクセスすると、Python はAttributeError:

class A:
    pass
a = A()
a.xyz

....
AttributeError: 'A' object has no attribute 'xyz'

Python は__getattr__() 、属性が存在しない場合にのみ呼び出します。1 つのユース ケースは、継承を使用する代わりに、別のオブジェクトのラッパーです。たとえばListWrapper、リストを使用するが、ホワイトリストに登録された属性のみを許可する を定義できます。

class ListWrapper:
    _allowed_attrs = set(['append', 'extend'])
    def __init__(self, value=None):
        self._wrapped = list(value) if value is not None else []
    def __getattr__(self, name):
        if name in self._allowed_attrs:
            return getattr(self._wrapped, name)
        else:
            raise AttributeError('No attribute {}.'.format(name))
    def __repr__(self):
        return repr(self._wrapped)

リストのように使用できます。

>>> my_list = ListWrapper('abc')
>>> my_list
['a', 'b', 'c']

追加要素:

>>> my_list.append('x')
>>> my_list
['a', 'b', 'c', 'x']

ただし、 で定義されているもの以外の属性は使用できません_allowed_attrs

my_list.index('a')
...

AttributeError: No attribute index.

ドキュメントの内容:

object.__getattr__(self, name)

属性ルックアップが通常の場所で属性を見つけられなかった場合に呼び出されます (つまり、インスタンス属性ではなく、self のクラス ツリーにも見つかりません)。name は属性名です。このメソッドは、(計算された) 属性値を返すか、AttributeError例外を発生させる必要があります。

属性が通常のメカニズムで見つかった場合、__getattr__()は呼び出されないことに注意してください。__getattr__()(これは、との間の意図的な非対称性__setattr__()です。) これは、効率上の理由と__getattr__()、インスタンスの他の属性にアクセスする方法がないためです。少なくともインスタンス変数については、インスタンス属性ディクショナリに値を挿入しない (代わりに別のオブジェクトに挿入する) ことで、完全な制御を偽造できることに注意してください。__getattribute__()属性アクセスを実際に完全に制御する方法については、以下の方法を参照してください。

于 2016-01-01T08:54:21.997 に答える