2011-09-07 13 views
12

Powtarzającym się schematem w moim programowaniu w języku Python na GAE jest pobieranie pewnej encji ze składnicy danych, a następnie ewentualna zmiana tej jednostki na podstawie różnych warunków. W końcu muszę .put() podmiot z powrotem do magazynu danych, aby zapewnić, że wszelkie zmiany, które mogły być wprowadzone do niego zostaną zapisane.Elegancki sposób na uniknięcie .put() na niezmienionych jednostkach

Często nie wprowadzano żadnych zmian, a ostateczna .put() to strata pieniędzy. Jak łatwo upewnić się, że umieszczam tylko jednostkę, jeśli naprawdę się zmieniła?

Kod może wyglądać

def handle_get_request(): 
    entity = Entity.get_by_key_name("foobar") 

    if phase_of_moon() == "full": 
     entity.werewolf = True 
    if random.choice([True, False]): 
     entity.lucky = True 
    if some_complicated_condition: 
     entity.answer = 42 

    entity.put() 

mogłem utrzymać „przemienieni” Flag którą ustawiony jeśli każdy stan zmienił podmiot, ale to wydaje się bardzo kruche. Jeśli zapomnę go gdzieś ustawić, to zmiany zostaną utracone.

Co skończyło się używając

def handle_get_request(): 
    entity = Entity.get_by_key_name("foobar") 
    original_xml = entity.to_xml() 

    if phase_of_moon() == "full": 
     entity.werewolf = True 
    if random.choice([True, False]): 
     entity.lucky = True 
    if some_complicated_condition: 
     entity.answer = 42 

    if entity.to_xml() != original_xml: entity.put() 

Nie nazwałbym to "elegancki". Byłoby elegancko, gdyby obiekt w końcu sam się ostatecznie uratował, ale czułem, że jest to na razie proste i czytelne.

+0

Co powiecie na mieszanie jednostki przed i po?To oczywiście wymaga stworzenia funkcji mieszającej, która może lub nie może być wykonalna dla twojego przypadku. – carlpett

+0

@carlpett Dzięki, zauważyłem, że db.Model ma już metodę to_xml(), której mogę użyć do porównania zamiast tworzyć własną funkcję skrótu . – Bemmu

Odpowiedz

4

Dlaczego nie sprawdzić, czy wynik jest równy (==) oryginał, a więc zdecydować, czy go zapisać. Zależy to od poprawnie zaimplementowanego __eq__, ale domyślnie powinno się to odbywać za pomocą porównania pól za polami w oparciu o __dict__.

def __eq__(self, other) : 
     return self.__dict__ == other.__dict__ 

(Należy pamiętać, że inne bogate operatory porównania i cebulą działać prawidłowo, jeśli to zrobisz See here.).

+0

Przypuszczam, że w tym przypadku potrzebowałbym jakiejś głębokiej kopii obiektu do porównania z późniejszymi zmianami. – Bemmu

+0

Dobrze, a ponadto, jak wspomniano, zależy to od "głębokiego" __eq__ tam, gdzie jest to potrzebne. –

+0

Przypuszczam, że w praktyce większość obiektów jest dość płytka, a dość płytkie kopiowanie wystarczy. Oczywiście zależy to od dokładnego wykresu obiektu. –

1

nie działa z GAE, ale w tej samej sytuacji, że mogę używać coś takiego:

entity = Entity.get_by_key_name("foobar") 
prev_entity_state = deepcopy(entity.__dict__) 

if phase_of_moon() == "full": 
    entity.werewolf = True 
if random.choice([True, False]): 
    entity.lucky = True 
if some_complicated_condition: 
    entity.answer = 42 

if entity.__dict__ == prev_entity_state: 
    entity.put() 
4

Jednym z możliwych rozwiązań jest użycie owijkę, która śledzi każdą zmianę atrybutu:

class Wrapper(object): 
    def __init__(self, x): 
     self._x = x 
     self._changed = False 

    def __setattr__(self, name, value): 
     if name[:1] == "_": 
      object.__setattr__(self, name, value) 
     else: 
      if getattr(self._x, name) != value: 
       setattr(self._x, name, value) 
       self._changed = True 

    def __getattribute__(self, name): 
     if name[:1] == "_": 
      return object.__getattribute__(self, name) 
     return getattr(self._x, name) 

class Contact: 
    def __init__(self, name, address): 
     self.name = name 
     self.address = address 


c = Contact("Me", "Here") 
w = Wrapper(c) 

print w.name    # --> Me 
w.name = w.name 
print w.name, w._changed # --> Me False 
w.name = "6502" 
print w.name, w._changed # --> 6502 True 
2

Ta odpowiedź jest częścią pytania, które napisałem o Python checksum of a dict . W odpowiedzi na to pytanie opracowałem metodę generowania sumy kontrolnej z a db.Model.

To jest przykład:

>>> class Actor(db.Model): 
... name = db.StringProperty() 
... age = db.IntegerProperty() 
... 
>>> u = Actor(name="John Doe", age=26) 
>>> util.checksum_from_model(u, Actor) 
'-42156217' 
>>> u.age = 47 
>>> checksum_from_model(u, Actor) 
'-63393076' 

zdefiniowałem te sposoby:

def checksum_from_model(ref, model, exclude_keys=[], exclude_properties=[]): 
    """Returns the checksum of a db.Model. 

    Attributes: 
     ref: The reference og the db.Model 
     model: The model type instance of db.Model. 

     exclude_keys: To exclude a list of properties name like 'updated' 
     exclude_properties: To exclude list of properties type like 'db.DateTimeProperty' 

    Returns: 
     A checksum in signed integer. 
    """ 
    l = [] 
    for key, prop in model.properties().iteritems(): 
     if not (key in exclude_keys) and \ 
       not any([True for x in exclude_properties if isinstance(prop, x)]): 
      l.append(getattr(ref, key)) 
    return checksum_from_list(l) 

def checksum_from_list(l): 
    """Returns a checksum from a list of data into an int.""" 
    return reduce(lambda x,y : x^y, [hash(repr(x)) for x in l]) 

Uwaga: Dla realizacji base36: http://en.wikipedia.org/wiki/Base_36#Python_implementation

Edytuj: Usunąłem zwrot w bazie36, teraz te funkcje działają bez zależności. (Porada od @Skirmantas)

+0

Dlaczego dokładnie base36? Możesz użyć base64, base32 lub base16 z python standart lib. – Ski

+0

@Skirmantas Tak, możesz użyć tego, czego chcesz. To źródło pochodzi z mojej implementacji i lubię base36, ponieważ uważam, że jest elegancki. – sahid

Powiązane problemy