Stilf: Zugriff auf privat markiete Variablen bei vergleichen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hi,

experimentiere gerade etwas mit pycharm herum um meinen Stil etwas zu verbessern.
Dabei ist mir dies Aufgefallen (ja der Hash ist hier nicht Unique aber für die Frage ist das nciht relevant)

Code: Alles auswählen

class A(object):
    def __init__(self):
        self._hash = 5

    def __hash__(self):
        return self._hash
Wie sollte ich nun __eq__ implementieren?

Code: Alles auswählen

    def __eq__(self, other):
        return self._hash == other._hash
Dies ist angeblich ein Stilfehler, da ich ja auf eine mit _ Markierte Variable einer anderen Klasse (other) Zugreife

Also so?

Code: Alles auswählen

    def __eq__(self, other):
        return self.__hash__() == other.__hash__()
Aber dann muss ich ach jedesmal die Funktion aufsführen die imme rnur einen statischen Wert zurück gibt (mein Hash ändert sich nie, da die Factoren, die ich in meiner Klasse als Hash verwende nicht geändert werden, bzw. es macht halt keinen Sinn diese zu ändern)

Wie sollte man es nun machen?
per _hash oder über den "langen" Weg __hash__() ?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hi,

Code: Alles auswählen

self._hash == hash(other)
wäre eine Möglichkeit. Damit schränktst du dich auch nicht darauf ein, dass "other" vom Typ "A", oder abgeleitet, sein muss. Wenn du das willst, dann darfst du eine Methode nicht "__hash__" nennen. Allerdings sollte ein Test auf Gleichheit niemals über Hashwerte geschehen. Damit ist nämlich nur eins garantiert: sind die Hashwerte verschieden, dann sind auch die Werte verschieden. Daher solltest du als erstest auf Gleichheit der Hashwerte testen. Sind diese verschieden, so kannst du "False" zurückliefern. Sind diese Gleich, so muss du tatsächlich zusätzlich auf Gleichheit prüfen.
Das Leben ist wie ein Tennisball.
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

EyDu hat geschrieben:Allerdings sollte ein Test auf Gleichheit niemals über Hashwerte geschehen. Damit ist nämlich nur eins garantiert: sind die Hashwerte verschieden, dann sind auch die Werte verschieden. Daher solltest du als erstest auf Gleichheit der Hashwerte testen. Sind diese verschieden, so kannst du "False" zurückliefern. Sind diese Gleich, so muss du tatsächlich zusätzlich auf Gleichheit prüfen.
Hi,

erstmal Danke für die Antwort.
Ich mache hier das __eq__ über den Hash da ich ein Objekt für einen Proxy habe ergo IP und Port speichern muss und natürlich ist ein Proxy eindeutig über
die IP und Port definiert. Ich baue mir laso einen Hash aus den beiden und habe somit immer sicher gestellt das gilt:

Code: Alles auswählen

self.__eq__(other) == (self._hash == other._hash)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

p90 hat geschrieben:Ich mache hier das __eq__ über den Hash da ich ein Objekt für einen Proxy habe ergo IP und Port speichern muss und natürlich ist ein Proxy eindeutig über
die IP und Port definiert. Ich baue mir laso einen Hash aus den beiden und habe somit immer sicher gestellt das gilt:

Code: Alles auswählen

self.__eq__(other) == (self._hash == other._hash)
Nein, das ist es gerade nicht. Nehmen wir mal an, das deine "__hash__"-Methode einen Integer liefert. Wenn sie das nicht tut, dann solltest du auch nicht "__hash__" verwenden, sondern irgend etwas anderes. Dann gilt genau das, was ich oben schon geschrieben habe: die Hashwerte können auch dann identisch sein, wenn es sich um verschiedene IP-Port-Kombinationen handelt. Theoretisch, ich habe jetzt die Möglichkeiten nicht durchprobiert. Allerdings würde ich mich auch nicht darauf verlassen, dass der Hash-Algorithmus immer der selbe ist. Das einzige, was dir der Vergleich zweier Hashwerte garantiert ist, dass verschiedene Hashwerte zu verschiedenen Werten gehören. Über identische Hashwerte wird keine Aussage getroffen.
Das Leben ist wie ein Tennisball.
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hm,

irgendiwe verstehe ich es noch nicht ganz:
Hier mal etwas mehr Code damit man vlt. was ich meine:

Code: Alles auswählen

import ipaddress

class _HashLogic(object):
	def __init__(self, obj_hash):
		self._hash = int(obj_hash)

	def __hash__(self):
		return self._hash

	def __eq__(self, other):
		if isinstance(other, _HashLogic):
			return self._hash == other._hash
		else:
			return NotImplemented

	def __ne__(self, other):
		if isinstance(other, _HashLogic):
			return self._hash != other._hash
		else:
			return NotImplemented

class Proxy(_HashLogic):
	def __init__(self, ip, port, *args, **kargs):
		self._ip = ipaddress.ip_address(ip)
		self._port = Port(port)
		_HashLogic.__init__(self, str(self._port) + str(int(self._ip)).zfill(39))
		#39 is the maximum length of a int(ip_address)
Der Hash ist hier also sowohl ein Integer als auch Eindeutig da ich ein Objekt vom Typ Proxy das den selben Port und die selbe IP hat als das selbe Objekt betrachte auch wenn es vlt. an einer anderen Speicheraddresse liegt.

Zur Erklärung warum ich das mache:

Ich will am Ende Dicts, Sets und Queues mit Proxies haben. Auf der einen Seite ist ein Prozess der immer neue Proxy Objekte erzeugt, ein Prozess testet ob diese (noch) funktionieren und einer speichert sie ab. Nun möchte ich eine effiziente Methode haben um schnell zu testen, ob ein Proxy bereits in einem von meinen Prozessen ist. Und dazu will ich die bereits gefunden in einem Set haben. Zwar sind meine Proxies hier nicht komplett immutable aber der Teil für den ich ein Set haben will ist es (die IP und der Port).

Vlt wird jetzt klarer was ich warum mache und wenn ich etwas furchtbar falsch mache warum ich es nicht verstehe.

Gehe aber jetzt erstmal ins Bett und nochmals Danke für die Antworten!
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

p90 hat geschrieben:Der Hash ist hier also sowohl ein Integer als auch Eindeutig da ich ein Objekt vom Typ Proxy das den selben Port und die selbe IP hat als das selbe Objekt betrachte auch wenn es vlt. an einer anderen Speicheraddresse liegt.
Theoretisch könnte dein Ansatz tatsächlich bijektiv sein, so dass man über die hash-Funktion auf Gleichheit testen kann. Allerdings sehe ich da ein kleines Problem bei der Erstellung des Strings für den Hash-Wert. Ich weiß ja nicht, was in ``self._ip`` drin steckt, allerdings sieht das ganz so aus, als wenn ``int(self._ip))`` nicht immer die selbe Anzahl an Stellen hat. Damit könnte man nicht mehr unterscheiden, ob eine Stelle vom Port kommt oder von der IP. Die Tupel aus Port und IP (42, 012) und (420, 12) würden einen identischen Hashwert liefern, obwohl sie nicht identisch sind.

Weiter hast du wohl nicht bedacht, dass 39 Zeichen für einen Hash-Wert etwas lang sein können. Ich zitiere dazu mal aus der Dokumentation:
http://docs.python.org/2/reference/datamodel.html#object.__hash__ hat geschrieben:Changed in version 2.5: __hash__() may now also return a long integer object; the 32-bit integer is then derived from the hash of that object.
Konkret bedeutet das: die __hash__-Methode liefert unter Umständen etwas anderes als den in ``self._hash`` gespeicherten Wert. Das Verhalten ist extrem unerwartet!

Ich würde deinen ganzen komplizierten Ansatz über den Haufen werfen und den Hashwert einfach aus dem Tupel von IP und Port bestimmen: ``hash((ip, port))`` nichts selbstgebasteltes oder andere seltsame Lösungen. Wenn du dann auf Gleichheit testen willst, dann testest du erst auf die Gleichheit von Hashwerten und ggf. noch auf Gleichheit der Werte. Das wird aber sehr selten notwendig sein, da nur selten Kollisionen auftreten werden. Wenn der Vergleich auf Gleichheit nicht Performancekritisch ist, was ich mal nicht annehme, dann würde ich auch gleich den Test auf Hashwerte lassen und direkt auf die Werte testen. Ob du nun zwei Integer vergleichst oder zweimal zwei, das ist vollkommen egal.
Das Leben ist wie ein Tennisball.
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

Hm,

ich glaube ich habe es jetzt begriffen.

Es gilt:
a == b -> hash(a) == hash(b)
Aber es gilt nicht:
hash(a) == hash(b) -> a == b

Habe meinen Code dementsprechend geändert (btw, wie würde ich diese Klasse am besten Immutable machen? Geht das wirklich nur über sowas wie erben von tuple und dann __new__ überschreiben etc. ?)

Code: Alles auswählen

class _HashLogic(object):
	"""Class enclosing the hashing logic for all classes"""
	def __init__(self, identifier):
		self._hash = hash(identifier)
		self._unique = identifier

	def __hash__(self):
		return self._hash

	def __eq__(self, other):
		if isinstance(other, _HashLogic):
			return self._unique == other._unique
		else:
			return NotImplemented

	def __ne__(self, other):
		if isinstance(other, _HashLogic):
			return self._unique != other._unique
		else:
			return NotImplemented
Damit bin ich aber wieder bei meinem vorherigen Problem. Sollte ich auf other._unique zugreifen oder nicht?
BlackJack

@p90: Die Klasse ist doch schon „immutable” — da ist nichts was man verändern könnte, ausser Implementierungsdetails, die ja wohl hoffentlich niemand anfasst.

Mit `__new__()` würde das ganze nur noch komplizierter. Da es eine Mixin-Klasse zu sein scheint, sollte man vielleicht statt einem besser zwei führende Unterstriche bei den Attributen machen. Auf der anderen Seite würde ich so eine Klasse überhaupt nicht verwenden weil das `__init__()` ja Mehrfachvererbung und der ganze Sch… der da mit dran hängt bedeutet. Ich sehe nicht so recht was an der Stelle hier gewonnen ist. Der Docstring ist auch sehr mutig formuliert. ;-)
p90
User
Beiträge: 198
Registriert: Donnerstag 22. Juli 2010, 17:30

BlackJack hat geschrieben:@p90: Die Klasse ist doch schon „immutable” — da ist nichts was man verändern könnte, ausser Implementierungsdetails, die ja wohl hoffentlich niemand anfasst.

Mit `__new__()` würde das ganze nur noch komplizierter. Da es eine Mixin-Klasse zu sein scheint, sollte man vielleicht statt einem besser zwei führende Unterstriche bei den Attributen machen. Auf der anderen Seite würde ich so eine Klasse überhaupt nicht verwenden weil das `__init__()` ja Mehrfachvererbung und der ganze Sch… der da mit dran hängt bedeutet. Ich sehe nicht so recht was an der Stelle hier gewonnen ist. Der Docstring ist auch sehr mutig formuliert. ;-)
Hm, okay.

Hatte das ganze in eine eigene Klasse ausgelagert, nachdem ich in drei Klassen fast genau die selben __eq__, __ne__ und __hash__
Funktion geschrieben hatte. Um nun nicht immer an drei Stellen den Code zu ändern wollte ich die Logik in einer Klasse zusammenfassen.

Hier sollte eigentlich erst etwas C++ Code stehen der beschreiben sollte was ich versuche in Python zu tun aber
nachdem ich jetzt wieder mal viel Python Doku gelesen habe scheint es das schon zu geben:
(noch nicht getestet, aber vlt. sieht man die Idee trotzdem)

Code: Alles auswählen

import collections
import ipaddress
class ProxyState(object):
    pass

class Proxy(collections.namedtuple("Proxy", "ip", "port", "state")):
    def __new__(self, ip, port):
        return collections.namedtuple.__new__(self, ipaddress.ip_address(ip), int(port), ProxyState())

Dann müsste ich mir den ganzen Kram mit Hash etc. verzichten können (bzw. vlt muss ich hier doch __hash__ und __eq__ überschreiben, mal sehen wie namedtuple diese handhabt)
Antworten