Möglichkeiten zur Suche bzw. Abfrage von Objekten

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.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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
Zuletzt geändert von mutetella am Montag 30. September 2013, 09:01, insgesamt 1-mal geändert.
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
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?
Benutzeravatar
pillmuncher
User
Beiträge: 1532
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

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:

Code: Alles auswählen

<__main__.Single object at 0x7fede80c>
Ich würde es aber nur unter bestimmten Bedingungen empfehlen, so etwa, wenn du eine Zustandsmaschine baust. Wozu brauchst du es denn?
In specifications, Murphy's Law supersedes Ohm's.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
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.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
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.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
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)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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... :wink:

Ich muss jetzt schlafen...

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
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.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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:

Code: Alles auswählen

Comparison = Not(Contains) if negation else Contains
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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
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.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
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. :-)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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.
  1. mehrere nicht zusammenhängende ( ;-) ) Daten und
  2. ein paar Strings
habe, dann müsste ich
  1. aus den Daten ein `Or`-Exemplar,
  2. aus den Strings je nach Wunsch ein `Or`- und/oder ein `And`-Exemplar machen und ...
  3. 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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
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.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Bin gleich soweit... 8)
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten