расширение класса, использующего __getattr__ (pygame.Rect)

Я пытаюсь создать свою собственную версию прямоугольника pygame.Rect, но с добавленной функцией, заключающейся в том, что, когда квадрат выходит за пределы определенных мировых границ, он появляется на другой стороне.

Это означает, что мне пришлось переписать множество функций pygame.Rect в моем расширении, и мне это удалось. Здесь нет проблем.

Проблемы начинаются, когда я пытаюсь изменить __getattr__ и __setattr__. pygame.Rect интенсивно использует эти функции, так что, например, запрос «верх» или «низ» относится к «y» и «y» + «ширина» соответственно. Я изменил эти функции, чтобы они соответствовали моей функции, но мне нужно сделать еще одну вещь: в функции __init__ мне нужно создать переменные worldwidth и worldheight. Но я не могу, так как функция __setattr__ этого не позволяет.

Вот что у меня есть прямо сейчас:

class Rectinworld(pygame.Rect):
    '''hides the fact that the world is round, when it comes to collisions and such'''

    __slots__ = ['_r']

    def __init__(self,(worldwidth,worldheight),*args):
        object.__setattr__(self, "worldwidth",worldwidth)
        if len(args)==0:
            super(Rectinworld, self).__init__((0,0,0,0))
        else:
            super(Rectinworld, self).__init__(*args)

def __getattr__(self, name):
    if name == 'top':
        return self._r.y
    #etc etc etc
    elif name == 'height':
        return self._r.h
    else:
        raise AttributeError(name)

def __setattr__(self, name, value):
    if name == 'top' or name == 'y':
        self._r.y = value % worldheight
    #etc etc etc
    elif name == 'height':
        if int(value) < 0:
            self._ensure_proxy()
        self._r.h = int(value)
    else:
        raise AttributeError(name)

Я оставил код в #etc etc etc комментариях для ясности. Код для pygame.Rect аналогичен: __setattr__ и __getattr__ не ссылаются на мировую ширину или мировую высоту, но в остальном они одинаковы, функция pygame.Rect .__ init__ очень длинная, но я думаю, что следующий фрагмент охватывает наиболее важные:

def __init__(self, *args):
#etc etc etc
if len(args) == 4:
            if args[2] < 0 or args[3] < 0:
                object.__setattr__(self, '_r', _RectProxy((int(args[0]),
                                                          int(args[1]),
                                                          int(args[2]),
int(args[3]))))

Полный код можно найти на странице https://github.com/brython-dev/brython-pygame/blob/master/pygame/rect.py.

Ошибка, которую я получаю сейчас:

line 10, in __init__
    object.__setattr__(self, "worldwidth",0)
AttributeError: 'Rectinworld' object has no attribute 'worldwidth'

Кажется, очевидным исправлением является добавление ширины мира и высоты мира к __slots__ и дальнейшее продвижение оттуда. Это давало еще более странные ошибки. При попытке установить любую переменную будет выдано следующее сообщение:

line 65, in __getattr__
    raise AttributeError(name)
AttributeError: _r

Короче говоря, мой вопрос сводится к следующему: почему я не могу создавать новые переменные и что мне нужно сделать, чтобы я мог?


person user2424345    schedule 27.11.2017    source источник
comment
вы должны использовать слоты и __setattr__? Разве вы не можете использовать self._worldwidth?   -  person furas    schedule 27.11.2017
comment
Я не уверен, что мне нужно использовать слоты. Я пытаюсь расширить класс, который его использует, поэтому имеет смысл использовать тот же стиль. Мне нужно использовать _ setattr_, если я хочу сохранить такие атрибуты, как 'top'. Я мог бы использовать self._worldwidth, но это не будет распознано функцией _ setattr_ и выдаст AttributeError   -  person user2424345    schedule 27.11.2017
comment
честно говоря, я бы сделал это по-другому. Я бы создал self._r = Rect() внутри class Rectinworld(object): и использовал бы @property   -  person furas    schedule 27.11.2017


Ответы (1)


Я не уверен, что это правильно, но у меня он работает (на Python 2 и 3)
и использует slots по вашему желанию.

import pygame

class Rectinworld(object):

    '''hides the fact that the world is round, when it comes to collisions and such'''

    __slots__ = ['_r', '_worldwidth', '_worldheight']

    def __init__(self, worldwidth, worldheight, *args):
        if not args:
            args = (0,0,0,0)

        super(Rectinworld, self).__setattr__("_r", pygame.Rect(args))
        super(Rectinworld, self).__setattr__("_worldwidth", worldwidth)
        super(Rectinworld, self).__setattr__("_worldheight", worldheight)

    def __getattr__(self, name):
        if name == 'top':
            return self._r.y
        #etc etc etc
        elif name == 'height':
            return self._r.h
        else:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        if name == 'top' or name == 'y':
            self._r.y = value % self._worldwidth
        #etc etc etc
        elif name == 'height':
            if int(value) < 0:
                self._ensure_proxy()
            self._r.h = int(value)
        else:
            raise AttributeError(name)

# --- test ---

r = Rectinworld(100, 100)
r.top = 120
print(r.top) # gives 20
person furas    schedule 27.11.2017
comment
Спасибо! Это хобби-проект, поэтому может потребоваться некоторое время, чтобы по-настоящему проверить, правильное ли это решение, но, похоже, он работает! - person user2424345; 28.11.2017