Seite 1 von 2
Möglichkeiten zur Suche bzw. Abfrage von Objekten
Verfasst: Freitag 22. Juli 2011, 19:26
von mutetella
Hallo,
ich glaube ja fast, ich verrenne mich da wieder in was saublödes...
Ich suche nach einer Möglichkeit, den Typ eines Objekts dynamisch zu ändern:
Code: Alles auswählen
class Multiple(list):
def __init__(self):
pass
def ever_method(self):
pass
def __norealideaofhowitcanwork__(self, newtype):
???
Ich stelle mir also folgendes vor:
Code: Alles auswählen
>>> m = Multiple()
>>> m
[]
>>> type(m)
<type 'list'>
>>> dir(m)
[....., 'ever_method', .....]
>>> m.__norealideaofhowitcanwork__(str)
>>> m
''
>>> type(m)
<type 'str'>
>>> dir(m)
[....., 'ever_method', .....]
>>> m.__norealideaofhowitcanwork__(None)
>>> m
>>>
>>> type(m)
<type 'NoneType'>
>>> dir(m)
[....., 'ever_method', .....]
Total krank oder geht sowas?
mutetella
Re: Klassentyp ändern
Verfasst: Freitag 22. Juli 2011, 19:29
von deets
Unter Umstaenden geht es. Aber wie waere es, wenn du zur Abwechslung mal das Problem erklaerst, und *dann* schauen wir mal, wie man es loesen kann?
Re: Klassentyp ändern
Verfasst: Freitag 22. Juli 2011, 19:33
von pillmuncher
Code: Alles auswählen
class Changeling(object):
def change(self, Class):
self.__class__ = Class
class Multiple(Changeling):
""" ... """
class Single(Changeling):
""" ... """
o = Multiple()
o.change(Single)
print o
Ergibt bei mir:
Ich würde es aber nur unter bestimmten Bedingungen empfehlen, so etwa, wenn du eine Zustandsmaschine baust. Wozu brauchst du es denn?
Re: Klassentyp ändern
Verfasst: Freitag 22. Juli 2011, 22:01
von mutetella
Ich arbeite an einer Klasse, die mir searchpatterns erstellt. In der Regel besteht ein solches pattern aus dem Namen des Attributes, das durchsucht werden soll und einer Liste von Suchtermen. Nun gibt es aber auch Fälle, in denen nur ein möglicher Suchterm sinnvoll ist.
Per default ist die Liste demnach ein 'list'-Objekt, ansonsten habe ich eine 'list'-Unterklasse 'OneItemList' gebastelt, die nur ein Element aufnimmt.
Soweit kein Problem.
Code: Alles auswählen
#searchterm is multiple
{'title': {'or_': ['Day', 'Night']}}
#searchterm is not multiple
{'date': datetime.date(2011, 7, 22)}
Nun ist es so, dass ein searchpattern, das nur einen Suchterm zulässt für Attribute angelegt wird, die nicht durchsucht sondern verglichen werden. Meine 'OneItemList' sollte also nach außen keine Liste, sondern eben das eine Objekt, das sich hinter 'self[0]' verbirgt, sein.
Nachdem ich dazu neige, die Pferde von hinten aufzuzäumen, hatte ich zuerst die verwegene Idee, den Typ jeweils an 'self[0]' anzugleichen weil ich nicht wusste, wie ich einerseits 'append()' und 'remove()' ohne Ansehen des Objektes verwenden könnte, andererseits aber eben entweder die Liste ('list') oder das eine Element ('OneItemList') zu erhalten.
Gelöst habe ich das jetzt vorerst mal so:
Code: Alles auswählen
class OneItemList(list):
def __init__(self):
list.__init__(self, [None])
def append(self, item):
self[:] = [item]
def remove(self, item):
if item in self:
self[:] = [None]
else:
raise ValueError
def __getslice__(self, *dummy):
return self[0]
Wenn ich somit [:] an die jeweilige Suchfunktion übergebe, landet dort entweder eine Liste oder das eine Element.
@pillmuncher:
Konntest Du bereits ahnen, worum es mir geht oder ist es Zufall, dass Du die Namen 'Multiple' und 'Single' verwendest...
mutetella
Re: Klassentyp ändern
Verfasst: Freitag 22. Juli 2011, 22:42
von deets
Ich glaube du hast ein Problem weil du an der falschen Stelle generische Datenstrukturen verwendest - und dich dann muehselig darum bemuehst, aus ihnen spezialisierte Objekte zu machen, so wie deine OneItemList.
Sowas wie eine Abfrage-Sprache baut man besser als Baum von Ausdruecken auf, die dann an ihren Blaettern entweder Konkrete Werte oder einen Identifier haben, der einen bestimmtes Attribut aus dem zu filternden Objekt rausholt.
Wenn das als Grundgeruest steht, kannst du dir immer noch ueberlegen, ob du daraus eine DSL via pyparsing machst, oder mit den Standard-Operatoren von Python klarkommst. Sieh dir zB mal SQLAlchemy's SQL-expressions an.
Re: Klassentyp ändern
Verfasst: Freitag 22. Juli 2011, 23:18
von mutetella
@deets:
Ich habe Termine, die 'title', 'longtext', 'begin' etc. haben. Ich schicke dem Termin ein searchpattern, z. B. {'title': {'and_': [Zahnarzt]}}, der Termin antwortet True oder False.
Was ist der Baum, was sind die Blätter?
mutetella
Re: Klassentyp ändern
Verfasst: Freitag 22. Juli 2011, 23:40
von deets
Wofuer ist denn das _and_? Und warum ist es *unterhalb* des Titels? Das wuerdest du doch auch in Python so nicht schreiben:
title == "Zahnarzt"
Also uebersetzt in Filter-Objekte
Code: Alles auswählen
Equal( # nimmt 2 Argumente, links & rechts
Attribute("title")
String("Zahnarzt")
)
[edit] String statt Literal, macht mehr Sinn.
Re: Klassentyp ändern
Verfasst: Samstag 23. Juli 2011, 09:58
von mutetella
Ich glaube, ich bin gar nicht so weit von dem entfernt, was Du meinst.
Hier mal ein Auszug davon, wie ich vorgehe, um einen Termin zu filtern:
Code: Alles auswählen
In [440]: p = searchtest.PatternFactory()
In [441]: p.add_collection('all')
In [442]: p.add_collection('category')
In [443]: p.switch('category', 'Niemand', 'or_')
In [444]: p.switch('category', 'Ich', 'or_')
In [445]: a = searchtest.Appointment()
In [446]: a.is_match('datum', p.get_pattern())
Out[446]: True
In [447]: p.switch('category', 'Ich', 'or_')
In [448]: a.is_match('datum', p.get_pattern())
Out[448]: False
In [449]: p.switch('category', 'Niemand', 'or_')
In [450]: p.switch('all', 'Zahnarzt', 'and_')
In [451]: a.is_match('datum', p.get_pattern())
Out[451]: True
In [452]: p.switch('all', 'Zahnarzt', 'and_')
In [453]: p.switch('all', 'unglaublich', 'not_')
In [454]: a.is_match('datum', p.get_pattern())
Out[454]: False
Ist mein Vorgehen grundsätzlich anderst?
'extended_contains()' entspricht doch Deinem 'Equal()', oder?
Oder sollte die eigentlich Suche gar nicht im Objekt ('Appointment') stattfinden?
mutetella
Re: Klassentyp ändern
Verfasst: Samstag 23. Juli 2011, 13:30
von deets
Doch, du bist da schon ein gutes Stueck weg, weil du die ganze Intelligenz in ein einziges Objekt packst - Appointment & seine is_match-Methode - und dafuer aber eine ziemlich simple Datenstruktur verwendest, welche nicht OO ist, sondern in is_match verstanden werden muss. Und darum ja auch dein Basis-Klassen-gehumse.
Ich schlage stattdessen Objekte vor, die bestimmte Dinge abpruefen & dann kombinierbar sind, uU sogar mit Python-operatoren, um eine DSL zu erzeugen. Etwa so:
Code: Alles auswählen
class Appointment(object):
def __init__(self, name, tags):
self.name, self.tags = name, tags
class Attribute(object):
def __init__(self, name):
self.name = name
def apply(self, app):
return getattr(app, self.name)
class Const(object):
def __init__(self, value):
self.value = value
def apply(self, _app):
return self.value
class Equal(object):
def __init__(self, left, right):
self.left, self.right = left, right
def apply(self, app):
return self.left.apply(app) == self.right.apply(app)
class IsIn(object):
def __init__(self, left, right):
self.left, self.right = left, right
def apply(self, app):
return self.left.apply(app) in self.right.apply(app)
termin_a = Appointment("python forum lesen", ["python", "freiwillig"])
termin_b = Appointment("putzen", ["unfreiwillig"])
expr = IsIn(Const("python"), Attribute("tags"))
print expr.apply(termin_a)
print expr.apply(termin_b)
Re: Klassentyp ändern
Verfasst: Samstag 23. Juli 2011, 23:48
von mutetella
Puh... das ist tatsächlich etwas anderes... viel einfacher und klarer als mein... wie hast Du geschrieben: ...'gehumse'
Ok, aus meiner 'PatternFactory' ist jetzt 'Filter' geworden...:
Code: Alles auswählen
import collections
import operator
def all_(iterable):
return all(iterable) if iterable else False
class Attribute(object):
def __init__(self, name):
self.name = name
def apply_(self, app):
return getattr(app, self.name)
class Term(object):
def __init__(self, value):
self.value = value
def apply_(self, dummy):
return self.value
class Equal(object):
def __init__(self, left, right, negation):
if negation:
self.eq_ = operator.eq
else:
self.eq_ = operator.ne
self.left, self.right = left, right
def apply_(self, app):
return self.eq_(self.left.apply_(app), self.right.apply_(app))
class Contains(object):
def __init__(self, left, right, negation):
if negation:
self.not_ = operator.not_
else:
self.not_ = lambda passthrough: passthrough
self.left, self.right = left, right
def apply_(self, app):
return self.not_(
self.left.apply_(app) in self.right.apply_(app))
class Filter(object):
def __init__(self):
self._expressions = collections.defaultdict(list)
def add_expression(self, term, attr_name, negation=False,
or_=False, multiple=True):
matching_type = 'any' if or_ else 'all'
Comparison = Contains if multiple else Equal
self._expressions[matching_type].append(Comparison(
Term(term), Attribute(attr_name), negation))
def apply_(self, entry):
return any(
[expr.apply_(entry) for expr in self._expressions['any']]) or \
all_(
[expr.apply_(entry) for expr in self._expressions['all']])
def reset(self):
self._expressions.clear()
class Appointment(object):
TEXT_ATTR = ['title', 'longtext']
def __init__(self):
self.title = 'Ich bin ein fieser Zahnarzttermin!'
self.begin = None
self.duration = None
self.category = ['Ich', 'Du', 'Alle']
self.longtext = 'Ein unglaublich langer Text!'
def _get_all_text(self):
return ' '.join(
[getattr(self, attr) for attr in Appointment.TEXT_ATTR])
all_text = property(_get_all_text)
Was mir noch nicht wirklich gefällt ist 'Contains.not_'. Mir fehlt irgendwie eine Vorstellung davon, wie ich 'operator.not_' mit 'operator.contains' in der Weise verbinde, dass ich eine 'not_in_'-Funktion erhalte.
Jedenfalls schon mal vielen Dank für Deine Inspiration, hat wieder etwas Licht ins Dunkel gebracht...
Ich muss jetzt schlafen...
mutetella
Re: Klassentyp ändern
Verfasst: Sonntag 24. Juli 2011, 01:32
von deets
Auch da schon wieder deutlich zu kompliziert gedacht:
Code: Alles auswählen
class Not(object):
def __init__(self, child):
self.child = child
def apply(self, app):
return not bool(self.child.apply(app))
Statt da irgendwie mit eq/neq rumzumachen. Und damit ist auch der contains-not-Operator sofort da.
Re: Klassentyp ändern
Verfasst: Sonntag 24. Juli 2011, 18:23
von mutetella
Ob ich wohl jemals von dieser 'allesmusskomprimiertinmöglichsteinezeile'-Denkweise wegkomme...
Zwei Punkte sind mir noch nicht so ganz klar:
1. Gibt es eine elegantere Möglichkeit, 'Not()' und 'Contains()' zu kombinieren, als diese if/else-Abfrage:
Code: Alles auswählen
if negation:
Not(Contains(Term(foo), Attribute(bar)))
else:
Contains(Term(foo), Attribute(bar))
Ich stelle mir etwas in der Art vor:
Nur: Wie kommen dann die Parameter zu 'Contains'? Im Grunde ein 'functools.partial' zur innersten Funktion/Klasse... Denkfehler?
2. Könnte man für den 'or'-Operator auch eine Klasse bilden? Das Problem ist hier, dass es bei 'or' und 'and' ja immer ein Gegenüber braucht, das ich ja nicht zwangsläufig habe... Noch ein Denkfehler?
mutetella
Re: Klassentyp ändern
Verfasst: Montag 30. September 2013, 08:19
von mutetella
Guten morgen!
Ich muss jetzt diesen Thread nochmals rauskramen, da nun das eingetreten ist, worauf ich auf meinen letzten Beitrag hier leider keine Antwort mehr bekam. Ich habe deets' (wo steckt der eigentlich?) Vorschlag folgendermaßen umgesetzt:
Code: Alles auswählen
class Attribute(object):
def __init__(self, name):
self.name = name
def submit(self, obj):
return getattr(obj, self.name)
class Constant(object):
def __init__(self, value):
self.value = value
def submit(self, obj=None):
return self.value
class Equal(object):
def __init__(self, left, right):
self.left, self.right = left, right
def submit(self, obj):
return self.left.submit(obj) == self.right.submit(obj)
class Or_(object):
def __init__(self, left, right):
self.left, self.right = left, right
def submit(self, obj):
return self.left.submit(obj) or self.right.submit(obj)
Soweit funktioniert das auch sehr schön:
Code: Alles auswählen
In [18]: date = datetime.date.today()
In [19]: attribute = Attribute('begin')
In [20]: pattern = Equal(Constant(date), attribute)
In [21]: pattern.submit(appt)
Out[21]: True
Wenn ich allerdings mehrere Daten abfragen möchte und dafür `Or_` verwende, bekomme ich (was mir inzwischen auch einleuchtet) einen Fehler:
Code: Alles auswählen
In [22]: for count in range(1000):
date = datetime.date.today() + datetime.timedelta(days=count)
pattern = Or_(pattern, Equal(Constant(date), attribute))
....:
In [23]: pattern.submit(appt)
RuntimeError: maximum recursion depth exceeded
Wenn ich also mit ``or`` (oder auch ``and``) arbeiten möchte, soll ich dann den Weg gehen:
Code: Alles auswählen
In [24]: patterns = set()
In [25]: for count in range(1000):
date = datetime.date.today() + datetime.timedelta(days=count)
patterns.add(Equal(Constant(date), attribute))
....:
In [26]: any(pattern.submit(appt) for pattern in patterns)
Out[26]: True
oder gäbe es eine Möglichkeit, das auch mit einer `Or_` Klasse zu lösen?
mutetella
Re: Klassentyp ändern
Verfasst: Montag 30. September 2013, 08:57
von BlackJack
@mutetella: Als erstes könnte man sich fragen ob der Benutzer tatsächlich so komplizierte Anfragen stellen wird wo 1000 rekursive Aufrufe gemacht werden müssen.
Dann könnte man die Rekursion selber programmieren, also selber in einer iterativen Funktion/Methode einen Stack mit noch aufzurufenden Tests in einer Liste verwalten.
Oder man lässt sich von Lisp/Scheme inspirieren und erlaubt der `Or`-Klasse nicht nur zwei sondern beliebig viele Operanden.
Re: Möglichkeiten zur Suche bzw. Abfrage von Objekten
Verfasst: Montag 30. September 2013, 09:15
von mutetella
BlackJack hat geschrieben:Als erstes könnte man sich fragen ob der Benutzer tatsächlich so komplizierte Anfragen stellen wird wo 1000 rekursive Aufrufe gemacht werden müssen.
Sicherlich werden solche Anfragen nicht häufig kommen, aber z. B. dann, wenn man die Termineinträge der letzten 3 Jahre haben möchte. Warum auch immer, aber funktionieren sollte das schon.
BlackJack hat geschrieben:Dann könnte man die Rekursion selber programmieren, also selber in einer iterativen Funktion/Methode einen Stack mit noch aufzurufenden Tests in einer Liste verwalten.
Damit meinst Du im Grunde das, was ich in meinem Minimalbeispiel mit `patterns = set()` gemacht habe?
BlackJack hat geschrieben:... und erlaubt der `Or`-Klasse nicht nur zwei sondern beliebig viele Operanden.
Den Stack also in der `Or_`-Klasse führen und dann dort auswerten?
mutetella
Re: Möglichkeiten zur Suche bzw. Abfrage von Objekten
Verfasst: Montag 30. September 2013, 09:38
von BlackJack
@mutetella: Wenn ich die Termine der letzten drei Jahre haben möchte, dann gibt es hoffentlich einen Abfrageoperator wie SQLs ``BETWEEN`` und ich muss nicht einen Vergleich für jeden Tag in den letzten drei Jahren schreiben.
Bei den letzten beiden Fragen hast Du die Frage und das Zitat aus meinem Beitrag vertauscht.

In Deinem Minimalbeispiel mit `set()` hast Du etwas ähnliches gemacht wie `Or` mit mehr als einem Argument. Und mit Rekursion selber programmieren war das mit der Liste mit einem eigenen Aufrufstapel gemeint. Allerdings nicht im `Or`. Das muss ja eine Funktion sein, die den gesamten Ausdruck auswertet und nicht nur `Or`-Verknüpfungen.
Das `Or` mit beliebig vielen Operanden (ungetestet):
Code: Alles auswählen
class Or(object):
def __init__(self, operands):
self.operands = operands
def submit(self, obj):
return any(operand.submit(obj) for operand in self.operands)
Damit hat man natürlich immer noch nicht das Problem von wirklich tief verschachtelten Ausdrücken gelöst. Dafür muss man sich eine Lösung basteln die den ansonsten impliziten Aufrufstapel mit einer Liste nachbildet und der von einer iterativen Funktion abgearbeitet wird. Dabei lernt man auf jeden Fall etwas.

Re: Möglichkeiten zur Suche bzw. Abfrage von Objekten
Verfasst: Montag 30. September 2013, 10:23
von mutetella
BlackJack hat geschrieben:Das muss ja eine Funktion sein, die den gesamten Ausdruck auswertet und nicht nur `Or`-Verknüpfungen.
Wenn ich also z. B.
- mehrere nicht zusammenhängende (
) Daten und - ein paar Strings
habe, dann müsste ich
- aus den Daten ein `Or`-Exemplar,
- aus den Strings je nach Wunsch ein `Or`- und/oder ein `And`-Exemplar machen und ...
- diese Exemplare innerhalb einer Funktion step-by-step auswerten?
BlackJack hat geschrieben:Damit hat man natürlich immer noch nicht das Problem von wirklich tief verschachtelten Ausdrücken gelöst.
Warum nicht?
mutetella
Re: Möglichkeiten zur Suche bzw. Abfrage von Objekten
Verfasst: Montag 30. September 2013, 10:58
von BlackJack
@mutetella: Ein `Or` (und `And`, …) mit variabler Anzahl von Operanden reicht nicht, da es ja nicht nur `Or` (und `And`) gibt, sondern man immer noch beliebig komplizierte Bäume aus den Ausdrücken aufbauen kann.
Bei der Beschreibung ist 3. der Knackpunkt. Deine `submit()` dürfen nicht mehr rekursiv andere `submit()`\s aufrufen und den Aufrufstapel der Laufzeitbibliothek überlassen, sondern man müsste das alles selber verwalten. Und damit auch die `submit()`-Methoden anders formulieren. Hier könnte man sicher ganz gut gebrauch von ``yield`` und `send()` machen. So etwas in dieser Richtung:
Code: Alles auswählen
class Equal(object):
def __init__(self, left, right):
self.left, self.right = left, right
def submit(self, obj):
left_value = yield (False, self.left.submit(obj))
right_value = yield (False, self.right.submit(obj))
yield (True, left_value == right_value)
Die Tupel geben einen Wahrheitswert zurück der anzeigt was das zweite Element ist: Ein Ergebnis von einem Unterausdruck der fertig ausgewertet ist; oder Generator für weitere Unterausdrücke die ausgewertet werden müssen, also im Grunde ein Stapelrahmen.
Re: Möglichkeiten zur Suche bzw. Abfrage von Objekten
Verfasst: Montag 30. September 2013, 14:53
von mutetella
Bin gleich soweit...

Re: Möglichkeiten zur Suche bzw. Abfrage von Objekten
Verfasst: Dienstag 1. Oktober 2013, 11:47
von mutetella
Oh Mann, das ist 'ne echt harte Nuss für mich...
Ich komme einfach nicht über folgende Hürde:
Der Einfachheit halber habe ich mich mal nur auf `left` beschränkt:
Code: Alles auswählen
class Equal(object):
def __init__(self, name, left):
self.name = name
self.left = left
def submit(self):
print self.name
left_value = yield self.left.submit()
print repr(left_value)
Wenn ich nun folgendes mache
Code: Alles auswählen
In [373]: e = Equal('top',
Equal('top_left', Equal('top_left_left', Constant(1))))
In [374]: top = e.submit()
In [375]: top.next()
top
Out[375]: <generator object submit at 0x2362cd0>
In [376]: _375.next()
top_left
Out[376]: <generator object submit at 0x2362d70>
In [377]: _376.next()
top_left_left
Out[377]: 1
In [378]: _376.next()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-378-11d76b112850> in <module>()
----> 1 _376.next()
StopIteration:
wandere ich bis zum untersten Level und erhalte dessen Wert, bleibe aber in jedem Level bei `yield` stehen. Ich vermute, `left_value` erhält auch nur `None`'s zugewiesen...
Wie soll ich denn da testen, ob
BlackJack hat geschrieben:Ein Ergebnis von einem Unterausdruck der fertig ausgewertet ist; oder Generator für weitere Unterausdrücke die ausgewertet werden müssen, ...
Mit anderen Worten: Wenn ich innerhalb `submit()` mittels `left_value = yield self.left.submit` eine Ebene tiefer gehe, bleibe ich doch immer beim `yield` dieser Ebene stehen, `left_value` ist immer `None`...
Und warum `left_value = (False, self.left.submit())`? Weshalb ein tuple? Weshalb nicht einfach `left_value = self.left_submit()`?
Ich möchte jetzt keine fertige Lösung, aber bitte den einen oder anderen Hinweis, ich dreh' mich völlig im Kreis...
mutetella