object is not iterable? Wieso iterable?

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
gotridofmyphone
User
Beiträge: 33
Registriert: Mittwoch 15. März 2017, 08:54

Hallo,

ich habe eine Klasse, deren Instanzen Knoten in einer Objekt-Hierarchie ("Baum") sind und verschiedene Read-Only-Eigenschaften haben. Instanzen können Eigenschaften fehlen. Werden sie trotzdem nachgefragt, soll die Anfrage unter der Haube an verlinkte höhere Knoten gegeben werden. Das Ergebnis soll zwischengespeichert und zurückgegeben werden. Für den Aufrufer soll es letztendlich keine Rolle spielen, ob die Eigenschaft von der Instanz selbst oder andere Instanzen, von denen sie "abgeleitet" ist. Dieser Ableitungsmechanismus hat nichts mit Python-Klassen zu tun. Es ist eine dynamische, vom User definierte hierarchische Struktur von Objekten.

Konkret ein Problem habe ich mit dynamisch generierten Properties (s. u. Zeile 64ff.), die nur einen Getter haben. Der Getter ist eine lambda-Funktion und hat nur die Aufgabe, eine Methode namens _lookup_attr() aufzurufen. Rufe ich die Property auf, kommt es zum Fehler:

Code: Alles auswählen

'ProtoPartial' object is not iterable
.

Ermittle ich die Eigenschaft direkt mittels Aufruf von _lookup_attr(), funktioniert die Sache. Diese Kurzproperties sind jetzt nicht zu wichtig, kann zur Not darauf verzichten (d.h. einfach _lookup_attr nach get() oder so umbenennen) , aber die Ursache der Fehlermeldung würde ich doch gerne verstehen, warum mein "syntactic sugar" nicht funktioniert.

Code: Alles auswählen

# -*- coding: utf-8 -*-

from __future__ import division
from Sompyler.synthesizer.sympartial import Sympartial
from Sompyler.synthesizer.modulation import Modulation
from Sompyler.synthesizer.envelope import Shape
import re

ABBREV_ARGS = {
    'A': 'attack',
    'S': "sustain",
    'B': "boost",
    'R': "release",
    'AM': "amplitude_modulation",
    'FM': "frequency_modulation",
    'WS': "wave_shape",
    'O': "oscillator" # value merely informational (not used)
}

ENV_ARGS = ('A', 'S', 'B', 'R')
OSC_ARGS = ('AM', 'FM', 'WS')

SHAPES = ENV_ARGS + ('WS',)
MODS = ('AM', 'FM')

class ProtoPartial(object):
    """
    Manage all properties 
    """

    __slots__ = ('_base', '_upper', '_cache') + tuple(
        '_' + i for i in ABBREV_ARGS
    )

    def __init__( self, base, upper, pp_registry, **args ):
        self._base = base
        self._upper = upper
        self._cache = {}

        for prop in ABBREV_ARGS.keys():
            value = args.get(prop)
            if value is None:
                setattr(self, '_' + prop, None)
                continue
            elif prop == "O":
                if isinstance(value, str):
                    pp = pp_registry['LOOK_UP'](value)
                    value = pp._lookup_attr('O')
            elif value.startswith('@'):
                value = value[1:]
                pp = pp_registry.get( value )
                if pp is None:
                    pp = pp_registry['LOOK_UP'](value)
                value = getattr( pp, prop)
            if prop in SHAPES:
                value = Shape.from_string(value)
            elif prop in MODS:
                value = Modulation.from_string(value, pp_registry)
            setattr(self, '_' + prop, value)

        if self._O is None:
            raise Exception("ProtoPartial instance missing oscillator")

    for i in ABBREV_ARGS:
        exec ('{0} = property(lambda (self, obj):'
            +' obj._lookup_attr("{0}"), None, None)'
        ).format(i)

    def _lookup_attr (self, attr):
        """ Look up attribute first in own attributes, then in the ancestry
            of named variation. If it is not found there, try the base and its ancestry.
        """

        value = getattr(self, '_' + attr, None)

        if value is not None:
            return value
        elif attr in self._cache:
            return self.cache[attr]

        for m in (self._upper, self._base):
            if m is None:
                continue
            privm = '_' + attr
            value = (
                getattr(m, privm) if hasattr(m, privm)
                                else m._lookup_attr(attr)
            )
            if value is not None:
                self._cache[attr] = value
                return value

    def sympartial ( self ):

        env_args = {}; osc_args = {}

        for each in ENV_ARGS:
             val = getattr(self, each)()
             if val: env_args[ ABBREV_ARGS[i] ] = val

        for i in OSC_ARGS:
             val = getattr(self, each)()
             if val: osc_args[ ABBREV_ARGS[i] ] = val

        return Sympartial(
            Envelope(**envelope_args), 
            self.O().derive(**osc_args)
        )
 

Danke schon mal,
gotridofmyphone
BlackJack

@gotridofmyphone: Wo und wobei tritt die Ausnahme denn auf?

Wobei ich bei der Verwendung von ``exec`` ja schon fast geneigt bin zu sagen das ich das gar nicht wissen will. ;-) Sind `__slots__` und ``exec`` hier tatsächlich nötig?

Könnte man das nicht einfach(er) mit `__getattr__()` lösen?
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@gotridofmyphone: für mich ist das alles zu magisch. »exec« sollte man nicht benutzen und wird hier auch überhaupt nicht gebraucht.

Wenn ich mal Deinen Code beiseite lasse und nur das lese, was Du möchtest, komme ich auf das:

Code: Alles auswählen

def lookup_property(name):
    return property(lambda self: self._lookup(name))

class HierarchicalProperties(object):
    def __init__(self, base, **properties):
        self._base = base
        self._properties = properties
        
    def _lookup(self, name):
        try:
            return self._properties[name]
        except KeyError:
            return self._base._lookup(name)
            
class Wasauchimmer(HierarchicalProperties):
    attack = lookup_property('attack')
    sustain = lookup_property('sustain')
    boost = lookup_property('boost')
    release = lookup_property('release')
    amplitude_modulation = lookup_property('amplitude_modulation')
    frequency_modulation = lookup_property('frequency_modulation')
    wave_shape = lookup_property('wave_shape')
    oscillator = lookup_property('oscillator')


parent = Wasauchimmer(None, attack=99, sustain=7)
child = Wasauchimmer(parent, boost=30)

print(child.attack)
Kannst Du beschreiben, was Du eigentlich willst?
gotridofmyphone
User
Beiträge: 33
Registriert: Mittwoch 15. März 2017, 08:54

Sirius3 hat geschrieben:@gotridofmyphone: für mich ist das alles zu magisch. »exec« sollte man nicht benutzen und wird hier auch überhaupt nicht gebraucht.

Wenn ich mal Deinen Code beiseite lasse und nur das lese, was Du möchtest, komme ich auf das:
Dein Code tut, was er soll und was ich will. Die acht Wiederholungen wollte ich durch exec eigentlich vermeiden. Ich dachte, genau dafür ist es da: Dynamischen Code zu parsen und auszuführen, als würde er literal anstelle des exec() im Quelltext stehen.
Kannst Du beschreiben, was Du eigentlich willst?
Das nötigste steht dazu in den ersten Absätzen meines Eingangspostings. Mehr mute ich diesem Thema eigentlich inhaltlich nicht zu weil off-topic und nerd language ahead und so weiter, wer will das wissen, und weil ich wiederum nicht weiß, was genau du eigentlich wissen möchtest. Akut möchte ich lernen, woher die Fehlermeldung stammt. Das habe ich nun, indem ich deinen Code studiert habe, selbst herausgefunden: "lambda (self, obj): ..." – das (self, obj) ist ein Tupel, klar, dass sich der Parser wundert, wenn an der Stelle kein Iterable steht. Keine Ahnung, wie ich auf (self, obj) gekommen bin. Ach ne, ich sehe, ich habe Deskriptoren und Property verwechselt. Properties erstellen Deskriptoren, stimmts?

So ginge es jetzt jedenfalls:

Code: Alles auswählen

class ProtoPartial(object):
    """
    Manage all properties. Inherit them top-down from base of the variation
    or from the protopartial labelled the same from an upper variation, and derive
    a sympartial instance comprising all properties by which you can render tones.
    """

    ...

    for i in ABBREV_ARGS:
        exec '{0} = property(lambda self: self._lookup_attr("{0}"))'.format(i)

    ...

Allerdings muss ich zugeben, dass es nicht sehr sinnvoll ist, erst dynamisch statische Properties zu definieren, um dann überwiegend doch wieder per getattr(...) auf diese Eigenschaften zuzugreifen. Von daher werde ich jetzt einfach _lookup_attr() zu get() umtaufen und als öffentliche Methode behandeln.

Black Jack: __getattr__? Das wäre die, oder besser gesagt eine andere Holzhammermethode. ;) Er soll wenigstens Typos abfangen. Die Methode würde die ja durchgereicht bekommen und ich müsste selbst gucken, ob es dieses Attribut gibt. Wäre mit zwei Zeilen erledigt, ja, aber schöner ist das auch nicht.
BlackJack

@gotridofmyphone: Ich sehe das ``exec`` immer noch als komisch und unnötig an. Wozu das in normalen Programm überhaupt gut sein soll? Keine Ahnung. Es geht jedenfalls auch ohne Quelltext als Zeichenkette zu erzeugen und dann zu kompilieren. Was irgendwie immer etwas unsauberes hat und zu dem man greift wenn die Sprache nicht mächtig genug ist. Ist sie aber. Man könnte das beispielsweise hinter das erstellen der Klasse schreiben (ungetestet):

Code: Alles auswählen

for name in ABBREV_ARGS:
    setattr(
      ProtoPartial,
      name,
      property(lambda self, name=name: self._lookup_attr(name))
    )
Oder man macht eine Decorator-Funktion daraus (ungetestet):

Code: Alles auswählen

def create_properties(names):
    
    def decorate(cls):
        slots = getattr(cls, '__slots__', ())
        slots += tuple('_' + name for name in names)
        cls.__slots__ = slots

        for name in names:
            setattr(
                cls,
                name,
                property(lambda self, name=name: self._lookup_attr(name)),
            )

        return cls

    return decorate
Ist etwas mehr Code, dafür aber auch für anderes anwendbar und erweitert auch die `__slots__` gleich mit:

Code: Alles auswählen

@create_properties(ABBREV_ARGS)
class ProtoPartial(object):
    __slots__ = ('_base', '_upper', '_cache')

    # ...
Gegen Typos kann man `__getattr__()` doch recht einfach absichern. Und statt eines `self._cache` könnte man die Attribute direkt auf dem Objekt setzen, von wo sie beim nächsten Abruf kommen können. Und die Methode an sich hast Du mit `_lookup_attr()` im Grunde bereits implementiert und lässt sie nur nicht automatisch aufrufen, sondern erzeugst extra Code um sie aufzurufen (ungetestet):

Code: Alles auswählen

    def __getattr__(self, name):
        if name not in ABBREV_ARGS:
            raise AttributeError(name)

        for obj in [self._upper, self._base]:
            if obj is not None:
                result = getattr(obj, name)
                if result is not None:
                    setattr(self, name, result)
                    return result

        return None
Antworten