Подтвердить что ты не робот

"Pythonic" путь к "reset" объектным переменным?

( "переменные" здесь относятся к "именам", я думаю, не совсем уверен в использовании определения pythonistas)

У меня есть объект и некоторые методы. Этим методам все нужны и все изменяют переменные объекта. Как я могу, в большинстве питонов и в лучшем случае уважать методы ООП, добиться того, чтобы переменные объекта использовались методами, но также сохраняли свои исходные значения для других методов?

Должен ли я копировать объект каждый раз, когда вызывается метод? Должен ли я сохранять исходные значения и использовать метод reset() для reset их каждый раз, когда метод их нуждается? Или есть еще лучший способ?

EDIT: Меня попросили ввести псевдокод. Поскольку меня больше интересует понимание концепции, а не просто решение проблемы, с которой я сталкиваюсь, я попытаюсь привести пример:

class Player():
    games = 0
    points = 0
    fouls = 0
    rebounds = 0
    assists = 0
    turnovers = 0
    steals = 0

    def playCupGame(self):
        # simulates a game and then assigns values to the variables, accordingly
        self.points = K #just an example

    def playLeagueGame(self):
        # simulates a game and then assigns values to the variables, accordingly
        self.points = Z #just an example
        self.rebounds = W #example again

    def playTrainingGame(self):
        # simulates a game and then assigns values to the variables, accordingly
        self.points = X #just an example
        self.rebounds = Y #example again

Вышеприведенный мой класс для объекта Player (для примера предположим, что он баскетбольный). Этот объект имеет три разных метода, которые присваивают значения статистике игроков.

Итак, скажем, у команды есть две игры в лиге, а затем в кубковой игре. Я должен был бы сделать эти звонки:

p.playLeagueGame()
p.playLeagueGame()
p.playCupGame()

Очевидно, что при выполнении второго и третьего вызовов ранее измененная статистика игрока должна быть reset. Для этого я могу либо написать метод reset, который возвращает все переменные в 0, либо скопировать объект для каждого вызова, который я делаю. Или сделать что-то совершенно другое.

Что, где мой вопрос лежит, какой лучший подход, python и oop wise?

ОБНОВЛЕНИЕ: Я подозреваю, что у меня есть superovercomplicated это, и я могу легко решить мою проблему, используя локальные переменные в функциях. Однако, что произойдет, если у меня есть функция внутри другой функции, могу ли я использовать локали внешнего во внутреннем?

4b9b3361

Ответ 1

Не уверен, достаточно ли этого "Pythonic", но вы можете определить "сбрасываемый" декоратор для метода __init__, который создает копию объекта __dict__ и добавляет метод reset(), который переключает текущий __dict__ в исходный.

Изменить - вот пример реализации:

def resettable(f):
    import copy

    def __init_and_copy__(self, *args, **kwargs):
        f(self, *args)
        self.__original_dict__ = copy.deepcopy(self.__dict__)

        def reset(o = self):
            o.__dict__ = o.__original_dict__

        self.reset = reset

    return __init_and_copy__

class Point(object):
    @resettable
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return "%d %d" % (self.x, self.y)

class LabeledPoint(Point):
    @resettable
    def __init__(self, x, y, label):
        self.x = x
        self.y = y
        self.label = label

    def __str__(self):
        return "%d %d (%s)" % (self.x, self.y, self.label)

p = Point(1, 2)

print p # 1 2

p.x = 15
p.y = 25

print p # 15 25

p.reset()

print p # 1 2

p2 = LabeledPoint(1, 2, "Test")

print p2 # 1 2 (Test)

p2.x = 3
p2.label = "Test2"

print p2 # 3 2 (Test2)

p2.reset()

print p2 # 1 2 (Test)

Edit2: добавлен тест с наследованием

Ответ 2

Я не уверен в "pythonic", но почему бы просто не создать метод reset в вашем объекте, который бы выполнял сброс? Вызовите этот метод как часть вашего __init__, чтобы не дублировать данные (т.е. Всегда (повторно) инициализировать его в одном месте - метод reset)

Ответ 3

Похоже, вы хотите знать, должен ли ваш класс быть неизменным объектом. Идея состоит в том, что после создания неизменяемый объект не может/не должен/не измениться.

В Python встроенные типы, такие как int или tuple экземпляры, неизменяемы, соблюдаются языком:

>>> a=(1, 2, 3, 1, 2, 3)
>>> a[0] = 9
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

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

>>> a=5000
>>> b=7000
>>> d=a+b
>>> d
12000
>>> id(d)
42882584
>>> d=a+b
>>> id(d)
42215680

Функция id() возвращает адрес объекта int 12000. И каждый раз, когда мы добавляем a+b, создается новый экземпляр объекта 12000.

Пользовательские неизменяемые классы должны выполняться вручную или просто выполняться как соглашение с комментарием исходного кода:

class X(object):
    """Immutable class. Don't change instance variables values!"""
    def __init__(self, *args):
        self._some_internal_value = ...

    def some_operation(self, arg0):
        new_instance = X(arg0 + ...)
        new_instance._some_internal_operation(self._some_internal_value, 42)
        return new_instance

    def _some_internal_operation(self, a, b):
        """..."""

В любом случае, это нормально, чтобы создать новый экземпляр для каждой операции.

Ответ 4

Я бы создал default dict в качестве элемента данных со всеми значениями по умолчанию, а затем __dict__.update(self.default) во время __init__, а затем снова в какой-то более поздней точке, чтобы вернуть все значения.

В более общем плане вы можете использовать крюк __setattr__, чтобы отслеживать каждую измененную переменную, а затем использовать эти данные для reset.

Ответ 5

Обратитесь к Memento Design Pattern, если вы хотите восстановить предыдущее состояние, или Proxy Design Pattern, если вы хотите, чтобы объект казался нетронутым, как будто только что созданный. В любом случае вам нужно поместить что-то между ссылкой и ссылкой.

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

# The Memento design pattern
class Scores(object):
    ...

class Player(object):
    def __init__(self,...):
        ...
        self.scores = None
        self.history = []
        self.reset()

    def reset(self):
        if (self.scores):
            self.history.append(self.scores)
        self.scores = Scores()

Ответ 6

Похоже, что в целом ваш дизайн нуждается в некоторой переработке. Что относительно класса PlayerGameStatistics, который будет отслеживать все это, и либо Player, либо Game будет содержать коллекцию этих объектов?

Кроме того, код, который вы показываете, является хорошим началом, но можете ли вы показать больше кода, который взаимодействует с классом Player? Мне просто трудно понять, почему один объект Player должен иметь методы PlayXGame - делает ли один Player не взаимодействующим с другим Player при игре, или почему конкретный Player играть в игру?

Ответ 7

Простой метод reset (называемый __init__ и повторно вызываемый при необходимости) имеет большой смысл. Но вот решение, которое, как мне кажется, интересно, если немного переработано: создайте контекстный менеджер. Мне любопытно, что люди думают об этом...

from contextlib import contextmanager

@contextmanager
def resetting(resettable):
    try:
        resettable.setdef()
        yield resettable
    finally:
        resettable.reset()

class Resetter(object):
    def __init__(self, foo=5, bar=6):
        self.foo = foo
        self.bar = bar
    def setdef(self):
        self._foo = self.foo
        self._bar = self.bar
    def reset(self):
        self.foo = self._foo
        self.bar = self._bar
    def method(self):
        with resetting(self):
            self.foo += self.bar
            print self.foo

r = Resetter()
r.method()    # prints 11
r.method()    # still prints 11

Чтобы переусердствовать, вы могли бы создать декоратор @resetme

def resetme(f):
    def rf(self, *args, **kwargs):
        with resetting(self):
            f(self, *args, **kwargs)
    return rf

Чтобы вместо явного использования with вы могли просто использовать декоратор:

@resetme
def method(self):
    self.foo += self.bar
    print self.foo

Ответ 8

Мне кажется, что вам нужно переделать свою модель, чтобы по крайней мере включить отдельный класс PlayerGameStats.

Что-то по строкам:

PlayerGameStats = collections.namedtuple("points fouls rebounds assists turnovers steals")

class Player():
    def __init__(self):
        self.cup_games = []
        self.league_games = []
        self.training_games = []

def playCupGame(self):
    # simulates a game and then assigns values to the variables, accordingly
    stats = PlayerGameStats(points, fouls, rebounds, assists, turnovers, steals)
    self.cup_games.append(stats)

def playLeagueGame(self):
    # simulates a game and then assigns values to the variables, accordingly
    stats = PlayerGameStats(points, fouls, rebounds, assists, turnovers, steals)
    self.league_games.append(stats)

def playTrainingGame(self):
    # simulates a game and then assigns values to the variables, accordingly
    stats = PlayerGameStats(points, fouls, rebounds, assists, turnovers, steals)
    self.training_games.append(stats)

И чтобы ответить на вопрос в вашем редактировании, да вложенные функции могут видеть переменные, хранящиеся во внешних областях. Вы можете узнать больше об этом в учебнике: http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces

Ответ 9

спасибо за хороший ввод, так как у меня была похожая проблема. Я решаю это с помощью метода init, так как я хотел бы иметь возможность reset в любом исходном состоянии объекта. Здесь мой код:

import copy
_tool_init_states = {}

def wrap_init(init_func):
    def init_hook(inst, *args, **kws):
        if inst not in _tool_init_states:
            # if there is a class hierarchy, only the outer scope does work
            _tool_init_states[inst] = None
            res = init_func(inst, *args, **kws)
            _tool_init_states[inst] = copy.deepcopy(inst.__dict__)
            return res
        else:
            return init_func(inst, *args, **kws)
    return init_hook

def reset(inst):
    inst.__dict__.clear()
    inst.__dict__.update(
        copy.deepcopy(_tool_init_states[inst])
    )

class _Resettable(type):
    """Wraps __init__ to store object _after_ init."""
    def __new__(mcs, *more):
        mcs = super(_Resetable, mcs).__new__(mcs, *more)
        mcs.__init__ = wrap_init(mcs.__init__)
        mcs.reset = reset
        return mcs

class MyResettableClass(object):
    __metaclass__ = Resettable
    def __init__(self):
        self.do_whatever = "you want,"
        self.it_will_be = "resetted by calling reset()"

Чтобы обновить начальное состояние, вы можете создать некоторый метод, например reset (...), который записывает данные в _tool_init_states. Надеюсь, это поможет кому-то. Если это возможно без метакласса, пожалуйста, дайте мне знать.

Ответ 10

Мне понравился (и попробовал) лучший ответ от PaoloVictor. Тем не менее, я обнаружил, что сам "reset", т.е. Если вы вызвали reset(), второй раз он выкинул исключение.

Я обнаружил, что он работал повторяемо со следующей реализацией

def resettable(f):
    import copy

    def __init_and_copy__(self, *args, **kwargs):
        f(self, *args, **kwargs)
        def reset(o = self):
            o.__dict__ = o.__original_dict__
            o.__original_dict__ = copy.deepcopy(self.__dict__)
        self.reset = reset
        self.__original_dict__ = copy.deepcopy(self.__dict__)
    return __init_and_copy__