web-dev-qa-db-ja.com

オブジェクトの変数を「リセット」する「Pythonic」の方法は?

(ここでの「変数」は「名前」を指しますが、pythonistasが使用する定義については完全にはわかりません)

オブジェクトといくつかのメソッドがあります。これらのメソッドはすべて、オブジェクトの変数を必要とし、すべて変更します。 OOPの手法を尊重し、最もPythonicで、最良の方法で、メソッドでオブジェクト変数を使用すると同時に、他のメソッドの元の値を保持するにはどうすればよいでしょうか。

メソッドが呼び出されるたびにオブジェクトをコピーする必要がありますか?元の値を保存し、メソッドがそれらを必要とするたびにそれらをリセットするための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オブジェクトの私のクラスです(たとえば、彼がバスケットボールのオブジェクトであると仮定します)。このオブジェクトには、プレーヤーの統計に値を割り当てる3つの異なるメソッドがあります。

つまり、チームに2つのリーグゲームと1つのカップゲームがあるとします。私はこれらの電話をかけなければならないでしょう:

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

2回目と3回目の呼び出しが行われたときに、以前に変更されたプレーヤーの統計をリセットする必要があることは明らかです。そのために、すべての変数を0に戻すリセットメソッドを作成するか、呼び出しごとにオブジェクトをコピーすることができます。または、まったく異なることをします。

それが私の質問の出番です。最善のアプローチは何ですか、pythonそしておっと賢明ですか?

PDATE:これが非常に複雑になっているのではないかと疑っています。関数でローカル変数を使用することで、問題を簡単に解決できます。ただし、別の関数内に関数がある場合はどうなりますか?内側の関数内で外側の関数のローカルを使用できますか?

16
user103798

それが「Pythonic」で十分かどうかはわかりませんが、オブジェクトのreset()メソッドを追加する__init__メソッドの「リセット可能な」デコレータを定義できます。現在の__dict__から元の__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:継承を伴うテストを追加しました

8
PaoloVictor

「Pythonic」についてはよくわかりませんが、オブジェクトにresetメソッドを作成して、リセットが必要な場合はどうすればよいでしょうか。このメソッドを__init__の一部として呼び出して、データを複製しないようにします(つまり、常に1か所で(再)初期化します-resetメソッド)

7
Bryan Oakley

すべてのデフォルト値を使用してデータメンバーとしてdefault dictを作成し、___init___中に__dict__.update(self.default)を実行し、その後、すべての値をプルバックします。 。

より一般的には、___setattr___フックを使用して、変更されたすべての変数を追跡し、後でそのデータを使用してそれらをリセットできます。

3
richo

クラスを 不変オブジェクト にする必要があるかどうかを知りたいようです。アイデアは、一度作成されると、不変オブジェクトは変更できない/変更すべきではない/変更されないということです。

Python では、intTupleインスタンスなどの組み込み型は不変であり、言語によって強制されます。

_>>> 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
_

別の例として、2つの整数を追加するたびに、新しいインスタンスが作成されます。

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

id() function は、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):
        """..."""
_

いずれにせよ、操作ごとに新しいインスタンスを作成しても問題ありません。

2
vz0

以前の状態を復元する場合は Mementoデザインパターン を、オブジェクトを表示する場合は プロキシデザインパターン を参照してくださいpristine、作成されたばかりのように。いずれにせよ、参照されているものとその状態の間にsomethingを置く必要があります。

コードが必要な場合はコメントしてください。ただし、デザインパターン名をキーワードとして使用すると、Web上でたくさん見つかると思います。

# 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()
2
Apalala

単純なリセットメソッド(__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
senderle

全体的に、デザインには多少の手直しが必要なようです。これらすべてを追跡するPlayerGameStatisticsクラスについてはどうでしょうか。また、PlayerまたはGameのいずれかがこれらのオブジェクトのコレクションを保持します。

また、表示するコードは良いスタートですが、Playerクラスと相互作用するコードをもっと表示できますか?単一のPlayerオブジェクトにPlayXGameメソッドが必要な理由を理解するのに苦労しています-単一のPlayerが他のPlayersと相互作用しない場合ゲームをプレイするのですか、それとも特定のPlayerがゲームをプレイするのはなぜですか?

1
user470379

私はPaoloVictorからの一番の答えが好きでした(そして試しました)。ただし、それ自体が「リセット」されることがわかりました。つまり、reset()を2回呼び出すと、例外がスローされます。

次の実装で繰り返し動作することがわかりました

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

同様の問題があったので、Nice入力に感謝します。オブジェクトの初期状態にリセットできるようにしたいので、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()"

初期状態を更新するには、データを_tool_init_statesに書き込むreset(...)のようなメソッドを作成します。これが誰かに役立つことを願っています。メタクラスなしでこれが可能な場合は、私に知らせてください。

0
Heiner

少なくとも別の「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

0
ncoghlan