«Pythonic» способ «обнулить» переменные объекта? - PullRequest
16 голосов
/ 01 февраля 2011

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

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

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

РЕДАКТИРОВАТЬ: меня попросили псевдокод.Поскольку меня больше интересует понимание концепции, а не просто конкретное решение проблемы, с которой я сталкиваюсь, я попытаюсь привести пример:

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()

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

Вот в чем заключается мой вопрос, каков наилучший подход, с точки зрения Python и OOP?

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

Ответы [ 10 ]

7 голосов
/ 01 февраля 2011

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

7 голосов
/ 01 февраля 2011

Не уверен, достаточно ли он "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: добавлен тест с наследованием

3 голосов
/ 02 февраля 2011

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

2 голосов
/ 01 февраля 2011

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

Вкл. 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):
        """..."""

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

1 голос
/ 02 февраля 2011

Простой метод сброса (вызывается в __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
1 голос
/ 02 февраля 2011

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

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

1 голос
/ 02 февраля 2011

См. Шаблон дизайна Memento , если вы хотите восстановить предыдущее состояние, или 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()
0 голосов
/ 17 января 2017

Мне понравился (и попробовал) лучший ответ от PaoloVictor. Однако я обнаружил, что он сам «сбрасывает», т. Е. Если вы вызываете 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__
0 голосов
/ 20 мая 2014

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

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.Надеюсь, это кому-нибудь поможет.Если это возможно без метакласса, пожалуйста, дайте мне знать.

0 голосов
/ 02 февраля 2011

Мне кажется, что вам нужно переделать модель, чтобы хотя бы включить отдельный класс 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

...