2017-11-04 102 views
9

Podklasowałem tuple lub używam namedtuple błogo na kilka lat, ale teraz mam przypadek użycia, gdzie potrzebuję klasy, która może być używana jako słaby referencyjny. A dzisiaj nauczyłem się tuples don't support weak references.niezmienne obiekty w Pythonie, które mogą mieć słabe referencje

Czy istnieje inny sposób na stworzenie niezmiennego obiektu w Pythonie ze stałym zestawem atrybutów? Nie potrzebuję indeksowania numerycznego ani zmiennej szerokości krotki.

class SimpleThingWithMethods(object): 
    def __init__(self, n, x): 
     # I just need to store n and x as read-only attributes 
    ... ??? ... 

Chyba rodzi się oczywiste pytanie, dlaczego niezmienne; Kod "Pythoniczny" zazwyczaj zakłada po prostu, że wszyscy jesteśmy tutaj dorośli i nikt przy zdrowych zmysłach nie sięgnie do klasy i nie obrzuci jej wartościami, jeśli ryzykuje zrujnowanie niezmienników klasy. W moim przypadku mam zajęcia w bibliotece i martwię się przypadkową modyfikacją obiektów przez użytkowników końcowych. Ludzie, z którymi pracuję, czasami robią niepoprawne założenia dotyczące mojego kodu i zaczynają robić rzeczy, których się nie spodziewałem, więc jest o wiele czystsze, gdybym mógł podnieść błąd, gdyby przypadkowo zmodyfikował mój kod.

Nie martwię się o kuloodporną niezmienność; jeśli ktoś naprawdę nikczemny chce iść i modyfikować rzeczy, ok, w porządku, oni są sami. Po prostu chcę, aby trudno było przypadkowo zmodyfikować moje obiekty.

+0

Co jest nie tak z obejściem sugerowanym przez Raymonda (w mailu)? – Elazar

+0

", aby utworzyć niestandardową klasę z relacją has-a, a nie relacją is-a."- (1) Chcę to zrobić w czystym Pythonie, (2), ma-a oznacza, że ​​klasa niestandardowa zawiera moją prawdziwą klasę, ale wtedy klasa niestandardowa nie jest niezmienna z powodu kurczaków i jaj - gdybym mógł stworzyć niezmienną klasę, to po prostu bym to zrobił –

+0

Domyślam się, że mógłbym użyć klasy kontenerowej, która przesłania '__eq__' i' __hash__' i deleguje do zawartego obiektu .Nie jest pewne, że działałoby to jednak dla słabych wierszy .. –

Odpowiedz

4

dobrze, to nie jest wielki odpowiedź, ale wygląda na to, mogę modyfikować odpowiedź w https://stackoverflow.com/a/4828492/44330 --- zasadniczo przesłaniając __setattr__ i __delattr__, aby spełnić moje potrzeby, przynajmniej wbrew przypadkowej modyfikacji. (Ale nie tak ładna jak podklasy tuple)

class Point(object): 
    __slots__ = ('x','y','__weakref__') 
    def __init__(self, x, y): 
     object.__setattr__(self, "x", x) 
     object.__setattr__(self, "y", y) 
    def __setattr__(self, *args): 
     raise TypeError 
    def __delattr__(self, *args): 
     raise TypeError 
    def __eq__(self, other): 
     return self.x == other.x and self.y == other.y 
    def __hash__(self): 
     return self.x.__hash__() * 31 + self.y.__hash__() 

Wdrażanie @ Elazar za pomysł:

class Point(object): 
    __slots__ = ('x','y','__weakref__') 
    def __new__(cls, x, y): 
     thing = object.__new__(cls) 
     object.__setattr__(thing, "x", x) 
     object.__setattr__(thing, "y", y) 
     return thing 
    def __setattr__(self, *args): 
     raise TypeError 
    def __delattr__(self, *args): 
     raise TypeError 
    def __eq__(self, other): 
     return self.x == other.x and self.y == other.y 
    def __hash__(self): 
     return self.x.__hash__() * 31 + self.y.__hash__()  
+1

Może zamiast tego zastąpię '__new__'. Aby zapobiec wywołaniu 'p .__ init __ (1, 2)'. – Elazar

+0

Ooh ... To jest niejasna, mroczna magia haha ​​... Wielu ludzi może chcieć spalić cię na popiół dla takiej herezji. Podoba mi się tak bardzo, to mam na myśli "równoległe myślenie", czasami trzeba być odważnym;). To jest plus BTW –

+0

Elazar, jak to zrobić? –

1

Co za pomocą enkapsulacji i abstrakcji od parametru (getter):

class SimpleThingWithMethods(object): 
    def __init__(self, n, x): 
     self._n = n 
     self._x = x 

    def x(self): 
     return self._x 

    def n(self): 
     return self._n 

    SimpleThingWithMethods(2,3).x() 

=> 3

+0

niezmienna. Proszę nie mówić mi, że mam problem z XY, a niezmienne zajęcia nie są Pythoniczne. (ps możesz użyć '@ property', aby uczynić ją nieco czystszą składnią) –

+1

@JasonS Nie powiedziałbym nic o XY, a czasami jesteś przywiązany do narzędzia (jak do pytona), więc kiedy to jest Sprawa, musisz otworzyć trochę umysłu i spróbować czegoś "nie narzędzie" lub "nie pyhonic", to znaczy, próbujesz osiągnąć cel, jeśli chcesz, to albo nie używasz Pythona, albo możesz spróbuj równoległego myślenia, opcja Cythona jest równoległym sposobem bardzo interesującym również: –

+0

OK, wystarczająco dobra. Zostałem ugryziony na tej stronie przez okropnych Zuzów Nazistów, którzy krzyczeli "Problem XY!" zbyt wiele, więc niestety szybko wyciągam wnioski, gdy ktoś wysyła odpowiedź, która ma inne założenia niż ja. –

2

Jeżeli nie martw się o isinstance kontrole można wzmocnić odpowiesz:

def Point(x, y): 
    class Point(object): 
     __slots__ = ('x','y','__weakref__') 
     def __setattr__(self, *args): 
      raise TypeError 
     def __delattr__(self, *args): 
      raise TypeError 
     def __eq__(self, other): 
      return x == other.x and y == other.y 
     def __hash__(self): 
      return x.__hash__() * 31 + y.__hash__() 
    p = Point() 
    object.__setattr__(p, "x", x) 
    object.__setattr__(p, "y", y) 
    return p 

Naprawdę nie polecam (każda inwokacja tworzy klasę!), Chciałem tylko zwrócić uwagę na możliwość.

Jest również możliwe, aby przejść javascript na całą drogę i dostarczyć __getattr__, który będzie mieć dostęp do zmiennych lokalnych. Ale to także spowolni dostęp, oprócz tworzenia. Teraz nie trzeba tych szczelin w ogóle:

class MetaImmutable: 
    def __setattr__(self, name, val): 
     raise TypeError 

def Point(x, y): 
    class Point(object): 
     __metaclass__ = MetaImmutable 
     __slots__ = ('__weakref__',) 
     def __getattr__(self, name): 
      if name == 'x': return x 
      if name == 'y': return y 
      raise TypeError 
     @property 
     def x(self): return x 
     @property 
     def y(self): return y 
     def __eq__(self, other): 
      return x == other.x and y == other.y 
     def __hash__(self): 
      return x.__hash__() * 31 + y.__hash__() 
    return Point() 

go przetestować:

>>> p = Point(1, 2) 
>>> p.y 
2 
>>> p.z 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 7, in __getattr__ 
TypeError 
>>> p.z = 5 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'Point' object has no attribute 'z' 
>>> object.__setattr__(p, 'z', 5) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'Point' object has no attribute 'z' 
>>> from weakref import ref 
>>> ref(p)().x 
1 
>>> type(p).x = property(lambda self: 3) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 4, in __setattr__ 
TypeError 

I wreszcie, nadal można złamać go:

>>> type.__setattr__(type(p), 'x', property(lambda self: 5)) 
>>> p.x 
5 

Ponownie zaleca nic tutaj . Użyj implementacji @Jasons.

+0

Nie widzę, co jest punktem opakowania. Być może próbujesz zablokować powtórne wywołania '__init__', ale każdy, kto by pomyślał, że to zrobił, prawdopodobnie po prostu nazwałby' object .__ setattr__'. (Przynajmniej do mnie przychodzi mi najpierw 'object .__ setattr__'.) Zamiast tego mógłbyś zaimplementować' __new__', tak jak skomentowałeś odpowiedź Jasona. – user2357112

+0

(1) wywołania funkcji '__init__' są dużo bardziej powszechne niż' object .__ setattr__'. Niektórzy ludzie nie uważają tego za hack. (2) Owijarka powoduje, że te wywołania powodują, że obiekt źle się zachowuje, co może udzielić im lekcji: P (3) poważnie, jeśli dodasz '__getattr__' staje się to dość kuloodporne – Elazar

+0

Uwaga: jeśli definiujesz' __eq__' , powinieneś także zdefiniować '__ne__' (na Pythonie 2 - i tutaj pytanie zawierało znacznik Pythona-2.7). –