Мне было интересно, если использование @property в python для обновления атрибута перезаписывает его или просто обновляет? Так как скорость в 2-х случаях сильно различается. И в случае перезаписи, какую альтернативу я могу использовать? Пример:

class sudoku:
def __init__(self,puzzle):
    self.grid={(i,j):puzzle[i][j] for i in range(9) for j in range(9)}
    self.elements
    self.forbidden=set()

@property
def elements(self):
    self.rows=[[self.grid[(i,j)] for j in range(9)] for i in range(9)]
    self.columns=[[self.grid[(i,j)] for i in range(9)] for j in range(9)]
    self.squares={(i,j): [self.grid[(3*i+k,3*j+l)] for k in range(3) for l in range(3)] for i in range(3) for j in range(3)  } 
    self.stack=[self.grid]
    self.empty={k for k in self.grid.keys() if self.grid[k]==0}

В основном я работаю с методом grid, и всякий раз, когда мне нужно обновить другие атрибуты, я вызываю elements. Я предпочитаю называть это вручную. Вопрос, однако, в том, что если я изменю self.grid[(i,j)], будет ли python вычислять каждый атрибут с нуля, потому что self.grid был изменен, или он изменяет только i-ю строку, j-й столбец и т. Д.?

Спасибо

Изменить: добавлен пример кода

0
Anass Elidrissi 17 Ноя 2017 в 16:11

1 ответ

Лучший ответ

Как бы то ни было, ваш вопрос совершенно неясен - но в любом случае, поскольку вы, кажется, не понимаете, что такое property и как он работает ...

class Obj(object):
    def __init__(self, x, y):
        self.x = x

    @property
    def x(self):
        return self._x / 2

    @x.setter
    def x(self, value):
        self._x = value * 2

Здесь у нас есть класс со свойством get / set ("привязка") x, поддерживаемым защищенным атрибутом _x.

Синтаксис "@property" здесь в основном является синтаксическим сахаром, вы можете написать этот код как

class Obj(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def get_x(self):
        return self._x / 2

    def set_x(self, value):
        self._x = value * 2

    x = property(fget=get_x, fset=set_x)

Единственное отличие от предыдущей версии состоит в том, что функции get_x и set_x остаются доступными как методы. Тогда, если у нас есть экземпляр obj:

obj = Obj(2, 4)

Затем

x = obj.x

Это просто ярлык для

x = obj.get_x()

А также

obj.x = 42

Это просто ярлык для

obj.set_x(42)

Принцип работы этого "ярлыка" полностью задокументирован здесь с целая глава, посвященная типу property.

Как видите, здесь нет ничего волшебного, и как только вы узнаете (без каламбура) протокол дескриптора и то, как его использует класс property, вы сможете сами ответить на вопрос.

Обратите внимание, что свойства ВСЕГДА будут добавлять некоторые накладные расходы (по сравнению с обычными атрибутами или прямым вызовом метода), поскольку у вас больше вызываемых уровней косвенного обращения и вызовов методов, поэтому лучше использовать их только тогда, когда это действительно имеет смысл.

РЕДАКТИРОВАТЬ: теперь вы разместили свой код, я подтверждаю, что вы не понимаете «свойства» Python - не только его техническую сторону, но даже базовую концепцию «вычисляемого атрибута».

Суть вычисляемых атрибутов в целом (встроенный тип property является лишь одной общей реализацией) состоит в том, чтобы иметь интерфейс простого атрибута (то, что вы можете получить, если с помощью value = obj.attrname, и в конечном итоге установить значение with obj.attrname = somevalue), но на самом деле вызывает геттер (и, в конечном итоге, сеттер) за капотом.

Ваше "свойство" elements, хотя технически реализовано как свойство только для чтения, на самом деле является методом, который инициализирует полдюжины атрибутов вашего класса, ничего не возвращает (ну, он неявно возвращает None) и какое возвращаемое значение фактически никогда не используется (конечно). Это определенно не то, для чего нужны вычисляемые атрибуты . Это НЕ должно быть свойством, это должна быть простая функция (с каким-то явным именем, например "setup_elements" или любым другим, имеющим здесь смысл).

# nb1 : classes names should be CamelCased
# nb2 : in Python 2x, you want to inherit from 'object'

class Sudoku(object):
    def __init__(self,puzzle):
        self.grid={(i,j):puzzle[i][j] for i in range(9) for j in range(9)}
        self.setup_elements()
        self.forbidden=set()

    def setup_elements(self):
        self.rows=[[self.grid[(i,j)] for j in range(9)] for i in range(9)]
        self.columns=[[self.grid[(i,j)] for i in range(9)] for j in range(9)]
        self.squares={(i,j): [self.grid[(3*i+k,3*j+l)] for k in range(3) for l in range(3)] for i in range(3) for j in range(3)  } 
        self.stack=[self.grid]
        self.empty={k for k, v in self.grid.items() if v==0}

Теперь отвечу на ваш вопрос:

если я изменю self.grid [(i, j)], будет ли python вычислять каждый атрибут с нуля, потому что self.grid был изменен

self.grid - простой атрибут, поэтому простое повторное связывание self.grid[(i, j)], конечно, не заставит "python" вычислить что-либо еще. Никакие другие атрибуты вашего объекта не будут затронуты. На самом деле Python (интерпретатор) не умеет читать мысли и будет делать только в точности то, о чем вы просили, ни меньше, ни больше, точка.

или он меняет только i-ю строку, j-й столбец

Этот :

obj = Sudoku(some_puzzle)
obj.grid[(1, 1)] = "WTF did you expect ?"

НЕ будет (я повторяю: « НЕ ») делать ничего кроме присвоения буквальной строки "WTF did you expect ?" значению obj.grid[(1, 1)]. Ни один другой атрибут не будет обновлен.

Теперь, если ваш вопрос был: "если я изменю что-то на self.grid и вызову self.setup_elements() после этого, Python пересчитает все атрибуты или обновит только self.rows[xxx] и {{X3 }} ", то ответ прост: Python будет делать в точности то, о чем вы просили: он выполнит self.setup_elements(), строка за строкой, инструкция за инструкцией. Легко и просто. Здесь нет никакой магии, и единственное , что вы получите, сделав его property вместо простого метода, это то, что вам не придется вводить () после вызвать метод.

Итак, если то, что вы ожидали от превращения этого метода elements() в свойство, заключалось в том, чтобы за сценой происходила какая-то невозможная магия, чтобы обнаружить, что вы на самом деле хотите только пересчитать затронутые элементы, то плохие новости, этого не произойдет вам нужно будет явно указать интерпретатору, как это сделать. Вычисленные атрибуты могут быть здесь частью решения, но не по волшебству - вам нужно будет написать весь код, необходимый для перехвата присвоений любому из этих атрибутов и пересчета того, что необходимо пересчитать.

Остерегайтесь, поскольку все эти атрибуты являются изменяемыми контейнерами, просто обернуть каждый из них в свойства будет недостаточно - учтите это:

class Foo(object):
    def __init__(self):
        self._bar = {"a":1, "b": 2}
    @property
    def bar(self):
        print("getting self._bar")
        return self._bar
    @bar.setter
    def bar(self, value):
        print("setting self._bar to {}".format(value))
        self._bar = value

>>> f = Foo()
>>> f.bar
getting self._bar
{'a': 1, 'b': 2}
>>> f.bar['z'] = "WTF ?"
getting self._bar
>>> f.bar
getting self._bar
{'a': 1, 'b': 2, 'z': 'WTF ?'}
>>> bar = f.bar
getting self._bar
>>> bar
{'a': 1, 'b': 2, 'z': 'WTF ?'}
>>> bar["a"] = 99
>>> f.bar
getting self._bar
{'a': 99, 'b': 2, 'z': 'WTF ?'}

Как видите, мы можем изменить self._bar без вызова функции bar.setter, потому что f.bar["x"] = "y" на самом деле НЕ назначается для f.bar (для чего потребуется f.bar = "something else") but _getting_ the f._bar dict thru the Foo.bar getter, then invoking setitem () `в этом dict.

Поэтому, если вы хотите перехватить что-то вроде f.bar["x"] = "y", вам также придется написать некоторый объект, подобный dict, который будет перехватывать все обращения к мутаторам в самом dict (__setitem__, но также и __delitem__ и т. Д. ) и уведомить f об этих изменениях и изменить ваше свойство так, чтобы оно вместо этого возвращало экземпляр этого dict-подобного объекта.

1
bruno desthuilliers 20 Ноя 2017 в 12:40