Flags setzen mit ``|``-Operator

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.
Benutzeravatar
snafu
User
Beiträge: 5537
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dienstag 19. April 2011, 09:11

Ich habe mich dem Thema mal gewidmet und zum Spass 2 kleine Funktionen geschrieben:

Code: Alles auswählen

def make_flag_values(length):
    if not isinstance(length, int) or length <= 0:
        raise ValueError, 'length must be a postive integer'
    return [1 << i for i in xrange(length)]

def has_flag(flags, value):
    return bool(flags & value)
Anwendung:

Code: Alles auswählen

>>> import shiftedflags
>>> shiftedflags.make_flag_values(3)
[1, 2, 4]
>>> foo, bar, baz = shiftedflags.make_flag_values(3)
>>> flags = foo | bar
>>> shiftedflags.has_flag(flags, foo)
True
>>> shiftedflags.has_flag(flags, bar)
True
>>> shiftedflags.has_flag(flags, baz)
False
Letzteres funktioniert, solange man mit "bekannten" Werten arbeitet. Etwas willkürliches funktioniert hingegen nicht:

Code: Alles auswählen

>>> shiftedflags.has_flag(flags, 23)
True
Sollte man dieses Problem gesondert behandeln oder sollte man einfach darauf vertrauen, dass der Benutzer hier keinen Mist macht? Und ja, mir ist klar, dass das schon mehr antik anmutender C-Stil ist und es bessere Möglichkeiten gibt. Ich wollt's eigentlich nur mal ausprobiert haben.
shcol (Repo | Doc | PyPi)
BlackJack

Dienstag 19. April 2011, 09:33

@snafu: Ich würde nur die beiden Ausdrücke bei ``return`` jeweils "inline" verwenden. Mir wären die Funktionen zu aufgebläht. Wobei ich solche Flags sowieso in eigenem Code zu vermeiden versuchen würde. Das ist, wie Du selbst ja schreibst, antik und C-Stil.

Wenn man mit solchem Code arbeiten muss, zum Beispiel wenn man eine C-API verwendet, dann wäre eine API schöner, die sowas hier erlaubt:

Code: Alles auswählen

MyFlags = make_flags_type(['foo', 'bar', 'baz'])
flags = MyFlags()  # Alle `False`.
flags = MyFlags(3)  # `flags.foo` und `flags.bar` gesetzt.
flags = MyFlags(MyFlags.foo | MyFlags.bar)  # Ditto.
flags.foo = True
flags.bar = True
print int(flags)  # -> 3
print flags.foo, flags.bar, flags.baz  # -> True True False
Ansonsten könnte man noch einige Methoden von `set()`\s implementieren, denn solche Flags sind ja im Grunde "bit sets".
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Dienstag 19. April 2011, 10:39

@BlackJack
Stellst du dir das so in etwa vor?

Code: Alles auswählen

class FlagObject(object):
    
    def __init__(self, flags=0):
        self._flagnames = None
        self._flags = flags

    def __int__(self):
        return self._flags

    def __getattr__(self, name):
        if name in dir(self._flagnames):
            return bool(self._flags & getattr(self._flagnames, name))

        try:
            return self.__dict__[name]
        except KeyError:
            raise AttributeError("{0} instance has no attribute " \
                                 "{1}".format(type(self).__name__, name))

    def __setattr__(self, name, value):
        if name.startswith("_"):
            self.__dict__[name] = value
            return

        if name in dir(self._flagnames):
            self._set(getattr(self._flagnames, name), value)

    def __getitem__(self, flag):
        return bool(self._flags & flag)

    def __setitem__(self, flag, boolean):
        self._set(flag, boolean)

    def __add__(self, flag):
        return self._flags | flag

    def __sub__(self, flag):
        return self._flags ^ flag

    def _set(self, flag, boolean):
        if boolean:
            self._flags |= flag
        else:
            self._flags ^= flag

    def set(self, flags=0):
        self._flags = flags


def make_flags_type(flagnames):
  
    class Flags(object):
        def __new__(self, flags=0):
            obj = FlagObject(flags)
            obj._flagnames = self
            return obj

    for flag, name in enumerate(flagnames):
        setattr(Flags, name, 2**flag)
    return Flags
Edit: Setter noch mit hinzugefügt.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Dienstag 19. April 2011, 14:24

snafu hat geschrieben:Letzteres funktioniert, solange man mit "bekannten" Werten arbeitet. Etwas willkürliches funktioniert hingegen nicht:

Code: Alles auswählen

>>> shiftedflags.has_flag(flags, 23)
True
Fällt mir nun auch gerade auf, da ja in der 23 quasi auch 1, 2 und 4 enthalten sind. Wenn man das unterbinden möchte, muss man einfach überprüfen ob der übergebene Wert mit dem Ergebnis übereinstimmt.

Code: Alles auswählen

def has_flag(flags, value):
    return value == flags & value
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
snafu
User
Beiträge: 5537
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dienstag 19. April 2011, 21:32

Xynon1 hat geschrieben:

Code: Alles auswählen

def has_flag(flags, value):
    return value == flags & value
Hübsch. Dürfte auch robust sein. Ich würde halt noch Klammern für den Ausdruck rechts vom Vergleichsoperator setzen, zwecks Lesbarkeit.
BlackJack hat geschrieben:Mir wären die Funktionen zu aufgebläht.
Ist ja mehr als ein "Wie geht das eigentlich..."-Beispiel gemeint. ;)
shcol (Repo | Doc | PyPi)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Mittwoch 20. April 2011, 06:31

Ich hatte mich genau im gleichen Moment damit beschäftigt, weil ich überlegt hatte wie ich das am besten bei meinem snake-Spiel mit den SDL-Flags für das ;enü mache. Hattest mich also genau richtig erwischt :D. Ich habe auch noch mal ein wenig geändert, siehe https://github.com/xynon/snake/blob/mas ... e/flags.py. z.B. bei dem entfernen einer Flag sollte man prüfen ob die Flag überhaupt noch *da* ist. Denn 0 ^ 32 ergibt wieder 32. Also würde man sie wieder hinzufügen, sprich wechseln. Das mag ganz nett sein, aber in meinem Menü nicht zu gebrauchen, weil jedes mal wenn das Menü geschlossen wird die Flags wechseln würden.

Bei dem Vergleich hatte ich tatsächlich überlegt ob ich die Klammern hinmache und bei genauerem Hinsehen sollte man das auch tun.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
snafu
User
Beiträge: 5537
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mittwoch 20. April 2011, 07:23

Xynon1 hat geschrieben:Bei dem Vergleich hatte ich tatsächlich überlegt ob ich die Klammern hinmache und bei genauerem Hinsehen sollte man das auch tun.
Naja, wenn einem die Gewichtung der Operationen bekannt ist, dann sollte das mit dem Verständnis kein Thema sein. Ich sag aber mal, jedes Mal, wo man zweimal hingucken muss, um etwas zu verstehen und es mit Klammern nur einmal hingucken wäre, würde ich halt zu Klammern neigen. Dürfte aber häufig eine persönliche Einschätzung sein. Ein zu übertriebener Einsatz von Klammern ist natürlich auch nicht so günstig.
shcol (Repo | Doc | PyPi)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Mittwoch 20. April 2011, 08:04

Ja, weshalb ich jetzt, einen halben Tag später, auch die Klammern rein gemacht habe :wink:

Würdest du, bzw. sollte man an meiner flags.py noch was ändern, hinzufügen oder rausnehmen? War ja im Prinzip mein erster Versuch BlackJacks gewünschte API für Flags (wenn man sie denn für eine C-API benötigt) umzusetzen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
snafu
User
Beiträge: 5537
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mittwoch 20. April 2011, 08:24

Ganz ehrlich? Ich hätte den Aufwand gar nicht erst betrieben. ^^

Übrigens, in Zeile 26 ist ein Tippfehler.
shcol (Repo | Doc | PyPi)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Mittwoch 20. April 2011, 08:40

Danke!
Und so ein Aufwand war das gar nicht, konnte man doch flüssig runter tippen :D . Nur das mit der __getattr__ und __setattr__ war ein wenig knifflig wegen der Rekursionsgefahr, ich hoffe das ich das so relativ sauber :| hinbekommen habe oder würdest du sagen das sich das Objekt dort eventuell nicht so verhalten würde wie erwartet.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
snafu
User
Beiträge: 5537
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mittwoch 20. April 2011, 08:54

Hm, naja. Wenn wir beim im Beispiel von BJ beschriebenen Verhalten bleiben, welches ich recht intuitiv finde, tut deine Implementierung nicht unbedingt dass, was man (ich) erwarten würde:

Code: Alles auswählen

>>> from flags import FlagObject
>>> flags = FlagObject()
>>> flags.foo = True
>>> flags.bar = True
>>> int(flags)
0 # hätte jetzt 3 erwartet
Du solltest hier wohl besser mit ``@property`` arbeiten. Ich persönlich finde es umständlich, wenn die Namen vorher festgelegt werden müssen.
Zuletzt geändert von snafu am Mittwoch 20. April 2011, 08:59, insgesamt 1-mal geändert.
shcol (Repo | Doc | PyPi)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Mittwoch 20. April 2011, 08:57

Für dieses Verhalten benötigst du auch die Funktion "make_flags_type" bzw. ein FlagTemplate Objekt, das FlagObject ist eigentlich *intern*. Zudem sollte man seine Flags vorher definieren und nicht Wahllos setzen. Aber hier müsste eigentlich ein AttributeError geworfen werden, da die flags nicht definiert sind, dann sperre ich aber die normale Flexibilität der Klasse.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
snafu
User
Beiträge: 5537
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mittwoch 20. April 2011, 09:02

Welche Nachteile bringt denn deiner Meinung nach ein dynamisches Festlegen (also die Zuweisung auf das Exemplar) mit sich?
shcol (Repo | Doc | PyPi)
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Mittwoch 20. April 2011, 09:06

AFAIK - die __setattr__ wird etwas größer und in der Klasse dürfen nur noch Variablen existieren die mit "_" beginnen. Zudem weiß ich so nicht ob ich "bar" nun den Wert 1, 2 oder 4 geben soll. Dafür könnte man noch was schreiben, aber wird das nicht unübersichtlich?
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
snafu
User
Beiträge: 5537
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mittwoch 20. April 2011, 09:35

Wie gesagt, es fragt sich, ob man das überhaupt so weit abstrahieren möchte. Wenn, dann würde ich einem frisch erstellten Attribut den nächstmöglichen Wert aus der Reihe ``1 << n`` geben. Ich muss gestehen, ich bin recht neu auf diesem Gebiet: Ein Setzen von ``False`` müsste das Attribut ja dann mit ``0`` belegen, oder? Zudem würde ich als Ergebnis vom Attributzugriff auf der abstrahierenden Klasse ja eher bloß ein ``True`` oder ``False`` zurückgeben (so übrigens auch von BJ gezeigt). Nur, wie kommt man dann an den Zahlenwert? ``int(flags.foo)`` dürfte wohl zu magisch sein, wenn ein ``flags.foo`` bloß ``True`` zurück gibt, oder?

Ich finde halt, der Fokus sollte darauf gelegt werden, dass sowas in der Art funktioniert (denke da gerade an (m)einen Formatter für Terminalausgaben):

Code: Alles auswählen

bold, blue, blink = make_flag_values(3)

def show_formatted_text(s, attrs=bold|blue|blink):
    flags = ShiftedFlags(attrs)
    if bold in flags: # bzw ``if flags.bold:`` bzw ``if flags & bold:`` bzw ``if attrs & bold:``
        [...]
Das wäre jetzt zumindest *meine* Idee von so einer Hilfsklasse. Der einzige Punkt, der *für* eine solche die Implementierung/Interpretation doch nicht unwesentlich verkomplizierende Verwendung gegenüber dem Einsatz von Tuples oder Listen spricht, wäre dass für den Anwender kein ``attrs=(bold,)`` nötig ist. Eigentlich weiß man bei einem Namen in Pluralform aber, dass etwas wie eine Liste erwartet wird. Im Grunde sind das alles nur Spielereien. Man könnte dem Anwender halt ein klein wenig Getippe ersparen. Andererseits existiert ja auch Enum und wird offensichtlich gelegentlich verwendet. ;)
shcol (Repo | Doc | PyPi)
Antworten