[Erledigt] Feststellen des (Eltern)Klassennames

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.
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

Hallo,

ich habe das Problem, daß ich dynamisch feststellen will, ob die Klasse eines Objekt mit einem String übereinstimmt:

Code: Alles auswählen

class base( object):
    def __init__( self):
        print "Base class"

class c1( base):
    def __init__( self):
        print "Class c1"

obj = c1()

if isinstrance( obj, base)
    print "Hurra!"

if magic_function( obj, "base")
    print "Hurra!"

Gibt es so eine magic_function?

Danke,

Christoph
Zuletzt geändert von ChrisGTJ am Freitag 28. September 2007, 11:11, insgesamt 1-mal geändert.
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Hoi,

Code: Alles auswählen

>>> class foo(): pass
... 
>>> foo.__name__
'foo'
Gruß,
Christian
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

Das funzt bei mir nicht, mache ich was falsch? Ich glaube aber auch nicht, daß es hilft, denn beide der folgenden Aussagen sollen geprüft werden und vor allem wahr sein:

Code: Alles auswählen

obj = c1()
obj.__name__ == "c1"
obj.__name__ == "base"
Gruß,

Christoph
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Warum brauchst du da einen string für? Mach doch isinstance(deine_instance, DeineKlasse)
TUFKAB – the user formerly known as blackbird
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

blackbird hat geschrieben:Warum brauchst du da einen string für? Mach doch isinstance(deine_instance, DeineKlasse)
Es geht um die Konfiguration eines Programmes. Per String sollen die notwendigen Voraussetzungen für einen Testlauf angegeben werden, diese Angabe soll möglichst einfach zu verstehen stehen sein.

Angenommen, es gibt zwei Produkte, die sich von einem Basisprodukt ableiten (base, c1 und c2). Es gibt Tests, die nur in Zusammenhang mit c1 sinnvoll sind und welche, die für beide durchgeführt werden müssen. Es soll nun geprüft werden, ob die Voraussetzungen mit der vorhandenen Testumgebung ausgeführt werden können.

Im Testcode soll es dann so aussehen:

Code: Alles auswählen


# <DeviceClass>.<DeviceName>.<weiteres>

TEST_NEEDS = ["c1.LinkerMotor",
              "c1.RechterMotor",
              "base.ObererMotor"]

Soll bedeuten, daß der Test drei Motoren braucht, nämlich 2 mal einen vom Typen c1 und einmal kann es ein c1 oder aber auch ein c2 Motor sein.

Man könnte es sicher noch anders lösen, aber wir wollen hier unabhängig von Pythonsyntax sein und einfach den String durchparsen.

Gruß,

Christoph
BlackJack

@ChrisGTJ: Du versuchst auf `__name__` auf einer Instanz zuzugreifen, während CM das für eine Klasse gezeigt hat. Von einer Instanz ginge das so: ``obj.__class__.__name__``.

Musst Du das denn unbedingt über den Typ prüfen? Schau doch einfach nach ob entsprechende Attribute auf den Objekten vorhanden sind.

Code: Alles auswählen

TEST_NEEDS = ('linker_motor', 'rechter_motor', 'oberer_motor')

def check(motors, attribute_names):
    for motor, attribute_name in zip(motors, attribute_names):
        try:
            getattr(motor, attribute_name)
        except AttributeError:
            return False
    return True
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Du könntest sowas machen:

Code: Alles auswählen

import sys

def sisinstance(obj, importname):
    if '.' not in importname:
        module = sys._getframe(1).f_globals['__name__']
        objname = importname
    else:
        module, objname = importname.rsplit('.', 1)
    mod = __import__(module, None, None, ['__name__'])
    return isinstance(obj, getattr(mod, objname))
Funktioniert dann so:

Code: Alles auswählen

>>> class Foo(object): pass
>>> class Bar(Foo): pass
>>> sisinstance(Bar(), 'Foo')
Wenn die Klasse in einem anderen Modul ist den ganzen Import Pfad angeben (sisinstance(obj, "package.module.MyClass")).
TUFKAB – the user formerly known as blackbird
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

BlackJack hat geschrieben:@ChrisGTJ: Du versuchst auf `__name__` auf einer Instanz zuzugreifen, während CM das für eine Klasse gezeigt hat. Von einer Instanz ginge das so: ``obj.__class__.__name__``.
Ah, ja, da war was. Danke für den Hinweis.

Wir möchten über den Typ prüfen, weil wir heute nicht wissen, was wir morgen haben werden. Also brauchen wir eine generische Geschichte. Die Idee mit dem String scheint uns daher sehr passend, zumal sie auch für einen nicht Pythonesen einfach zu verstehen ist (z.B., wenn man die Klassennamen als Schlüsselwörter vorgibt).

@blackbird:
Hm, ich muß zugeben, ich verstehe nicht so ganz, was Du da tust, allerdings klappt das getattr nicht, weil (wie Du vorausgesehen hast) die Klasse in einem anderen Modul steckt. Das ist für uns aber nicht praktizierbar, weil der Testschreiber dann Kenntnisse über das Framework haben müßte (die er weder hat, noch haben will).

Eine Lösung wäre natürlich die Implementation einer Methode check_name:

Code: Alles auswählen


class base( object):
     def check_name( self, name):
        return name == "base"

class c1( base):
    def check_name( self, name):
        if name == "c1":
            return super( c1, self).check_name( name)
        return False

Aber ist das nicht unelegant? Ich mein, Python ist so mächtig und bietet so viele lustige Features ;)....

Christoph
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Spricht was dagegen das so zu machen?

Code: Alles auswählen

class A(object):
    pass
        
class B(A):        
    pass
    
a = A()
b = B()

g = globals()
print isinstance(a, g['A'])
print isinstance(a, g['B'])
print isinstance(b, g['A'])
print isinstance(b, g['B'])
BlackJack

@ChrisGTJ: Auf Typen zu prüfen oder gar noch auf konkrete Namen ist doch aber total unflexibel. Das geht mit der Aussage, dass ihr nicht wisst was ihr morgen haben werdet nicht zusammen. ``isinstance()`` berücksichtigt wenigstens noch Vererbung, aber auf einen Namen prüfen legt einen wirklich auf exakt eine Klasse fest.

Warum testet Du nicht einfach ob die entsprechenden Attribute auf dem Objekt, egal von welchem Typ auch immer das ist, vorhanden sind.

``super()`` brauchst Du übrigens nur, wenn Du "diamantförmige" Mehrfachvererbung hast.
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

@zap:

Danke, ich habe es eben ausprobiert, funzt leider nicht. Ich weiß nicht, woran es liegt, aber meine globals enthalten gar nicht ganzen Klassen, die definiert sind, also bekomme ich mal wieder eine Exception :( Vielleicht ist der Kontext, in dem ich globals() aufrufe nicht der richtige...

@BlackJack:

Auf der einen Seite ist es unflexiebel, ja. Das soll auch so sein, denn wenn der geschriebene Test nicht für die vorhandene Hardware geeignet ist, will ich ihn nicht ausführen.

Andererseits soll die Flexibilität ja drin sein, indem ich eben nicht nur auf die Klasse prüfe, sondern auf die übergeordneten Klassen auch, wie ich oben ja schon beschrieben habe. Das dumme ist nur, daß ich im Moment nicht vom String zur Klasse komme.

Wegen der Attribute: Ich denke, der Klassenname (oder der der Elternklasse) ist schon Attribut genug, sozusagen. Soll heißen, wenn der Klassenname zur Instanz paßt, weiß ich, was ich wissen will. Wozu noch andere Attribute hinzufügen?

Zu dem super():
Steh auf dem Schlauch. wie soll denn sonst die Elternklasse aufgerufen werden?

Da fällt mir auf (nach dem Lesen der Doku zu super()): Ich könnte mich ja mit super() durch die Klassenhierarchie hangeln und dann mit isinstance() prüfen. Das probier ich mal...

Gruß,

Christoph
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Was der Blackjack sagen wollte mit den Attributen geht wahrscheinlich mehr in Richtung Duck-Typing. Also erst garnicht abprüfen was für ein Typ ein Objekt hat sondern einfach mit den übergebenen Objekt arbeiten und erwarten das es etwas kann, wenn es das nicht kann soll es halt ignoriert werden.

So machst du deinen Code viel leichter erweiterbar. Und kannst bei Bedarf neue Module oder Klassen hinzufügen und deine Prüfmethoden darauf anwenden.

Denk mal drüber nach das ist oft die beste Vorgehensweise, auch wenn man es vorher nicht glaubt ;)

Edit: Was das globals angeht müsste ich mir erstmal selber durchlesen wie das genau aussieht. Kann mir vorstellen das der Inhalt ja nach Sichtweise unterschiedlich ausfällt. (sagt ja auch der docstring ;) )
Kann durchaus sein, dass das keine so gute Lösung dafür ist.
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Ach zu deiner Frage mit dem super.
Der einfachste Weg ist der:

Code: Alles auswählen

In [80]: class A:
   ....:     def foo(self):
   ....:         print "basic foo"
   ....:
   ....:

In [81]: class B(A):
   ....:     def foo(self):
   ....:         A.foo(self)
   ....:         print "my foo"
   ....:
   ....:

In [82]: o = B()

In [83]: o.foo()
basic foo
my foo
poker
User
Beiträge: 146
Registriert: Donnerstag 20. September 2007, 21:44

ChrisGTJ hat geschrieben: Da fällt mir auf (nach dem Lesen der Doku zu super()): Ich könnte mich ja mit super() durch die Klassenhierarchie hangeln und dann mit isinstance() prüfen. Das probier ich mal...
Warum? Nimm doch ``__mro__``.

Code: Alles auswählen


In [3]: class Foo(object):
   ...:     pass
   ...:
In [4]: class Bar(Foo):
   ...:     pass
   ...:
In [5]: class Spam(Bar):
   ...:     pass
   ...:
In [6]: Spam().__class__.__bases__
Out[6]: (<class '__main__.Bar'>,)
In [7]: Spam().__class__.__mro__
Out[7]:
(<class '__main__.Spam'>,
 <class '__main__.Bar'>,
 <class '__main__.Foo'>,
 <type 'object'>)
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

Hallo werte Mitleser!

Ich wollte nur noch mal Danke für Eure Hilfe sagen und zeigen, wie wir das Problem letztlich gelöst haben, falls jemand Interesse daran hat oder ein ähnliches Problem hat...

Aaaalso, eine Möglichkeit, aus dem String eines Klassennamens eine Instanz der Klasse zu bauen, haben wir nicht gefunden, so daß das ursprüngliche Ziel, aus dem String

Code: Alles auswählen

TEST_NEEDS = ["c1.LinkerMotor",
              "c1.RechterMotor",
              "base.ObererMotor"]
die Vererbungshierarchie der Klassenangaben "c1" und "base" zu ermitteln, nicht erreicht werden kann. Wir machen es nun wie folgt:

Code: Alles auswählen

class base( object):
    pass

class c1( base):
    def __init__( self, name):
        self.name = name

class c2( base):
    def __init__( self, name):
        self.name = name

TEST_NEEDS = [(c1, "LinkerMotor"),
              (c1, "RechterMotor"),
              (base, "ObererMotor")]

def checkMotor( motor):
    ret = False
    for r in TEST_NEEDS:
        # Den Klassennamen der geforderten Klasse bestimmen:
        classClass = r[0]
        motorName = r[1]
        className = classClass.__name__

        # Bestimmen, welche Elternklassen
        # die übergebene Motorinstanz hat:
        classTuple = motor.__class__.__mro__
        # Wir haben jetzt ein Tupel: ("<class '__main__.c1'>",
        # "<class '__main__.base'>", "<type 'object'>")
        ret = False
        for subClassString in classTuple:
            # Uns interessiert ja nur der Klassenname
            subClasses = str(subClassString).split("'")[1]
            subClass = subClasses.split('.')[-1]
            if subClass == className:
                ret = True
                break
        if ret:
            if motor.name == motorName:
                break
            else:
                ret = False
    return ret
# Ende checkMotor
# -----------------------------------------

m1 = c1( "LinkerMotor")
result = checkMotor( m1)
print result
m2 = c2( "ObererMotor")
result = checkMotor( m2)
print result
m3 = c2( "LinkerMotor")
result = checkMotor( m3)
print result

Das Ergebnis ist wie erwartet, für die Motoren m1 und m2 paßt die Konfiguration, für Motor m3 nicht.

Danke für's zuhören ;),

Christoph
BlackJack

Das ist recht umständlich. Informationen aus einer Zeichenkettendarstellung zu parsen anstatt auf diese direkt zuzugreifen ist zum Beispiel fehleranfällig. `__mro__` enthält Klassen- oder Typ-Objekte, die haben ein `__name__`-Attribut, und das freundlicherweise sogar ohne den "Pfad" durch die Module und Packages.

`ret` und einiges an Prüfcode könnte man einsparen wenn man am Ende ein ``return True`` schreibt und die Funktion verlässt mit ``return False`` verlässt, wenn eine der Bedingungen nicht erfüllt ist.

Und ich verstehe immer noch nicht warum der Klassenname wichtiger ist als die Funktionalität, die die Klasse bietet.

Das ganze etwas kompakter:

Code: Alles auswählen

from inspect import getmro
from itertools import imap

def check(motor, needs):
    def check_need((class_, motor_name)):
        class_names = (c.__name__ for c in getmro(motor.__class__))
        return class_.__name__ in class_names and motor.name == motor_name
    
    return any(imap(check_need, needs))
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

Hi BlackJack,

das wird ja wieder mal eine Lehrstunde in Python, prima, danke dafür! Python ist derartig vielfältig, daß ich aus der (beschränkten?) Sicht meiner C Erfahrungen gar nicht auf die Idee komme, daß da noch was ist. Allein die Geschichte mit

Code: Alles auswählen

result = b in liste
hat mich mal wieder völlig Überrascht...

Ich habe Deinen Code mal übernommen und ausgeführt, bekomme aber eine Exception:

Code: Alles auswählen

Traceback (most recent call last):
  File "D:\Program Files\eclipse 3.3\plugins\org.python.pydev.debug_1.3.8\pysrc\pydevd.py", line 575, in trace_dispatch
    return dbFrame.trace_dispatch(frame, event, arg)
  File "D:\Program Files\eclipse 3.3\plugins\org.python.pydev.debug_1.3.8\pysrc\pydevd_frame.py", line 53, in trace_dispatch
    for b, condition, func_name in breakpoint.values():
GeneratorExit
Exception exceptions.SystemError: 'error return without exception set' in <generator object at 0x00BF64E0> ignored
Könnte das das Ende des Generators sein? Wenn ich das Skript ohne den Debugger laufen lasse, sehe ich die Exception nicht.

Frage nebenbei: Wieso baut die folgende Zeile überhaupt einen Generator?

[code = py]
class_name = (c.__name__ for c in getmro(motor.__class__))
[/code]


Was die Frage nach der Frage angeht:
Ich verstehe nicht, was Du nicht verstehst ;). Soll heißen, daß für mich die Aufgabenstellung aus unserem Kontext heraus völlig klar und logisch ist, und daher auch unser Lösungsansatz. Aber dieser Lösungsansatz beruht möglicherweise auf einem zu geringen Verständnis der Konzepte von Python und deren Möglichkeiten.

Wir machen es uns hier auch vielleicht etwas einfach, kann sein. Du hast in einem früheren Beitrag hierzu gefragt, warum wir nicht auf Attribute abtesten. Möglicherweise denke ich zu kurz, aber nehmen wir an, eines der Attribute ist die Anbindung an die Steuerung, einmal über einen CAN Bus und zum anderen mal über eine RS232 Leitung. Dann gibt es mindestens zwei Testkategorien:

1.) Tests, die die Fähigkeiten des Motors testen, bei denen das Kommunikationsmedium völlig egal ist.
2.) Tests, die die Kommunikation selber testen oder aber spezielle Möglichkeiten des Kanals ausnutzen.

Wenn wir jetzt eine Testsuite aufbauen, dann möchten wir klassifizieren und abgleichen können,
- was ein spezieller Test benötigt und
- was ein Testaufbau hergibt.

Der Testentwickler kann nun einfach (na ja, da das mit dem String nicht geht, ist es nicht mehr ganz so einfach...) aufschreiben, was sein Test erfordert. Wir wollen bewußt eine feste Syntax vorgeben, damit sich der Anwender klar wird, was er tut (im Gegensatz zu dem, was er tun will ;))

Reicht das an Erklärung? Wenn es Ideen gibt, das auf anderem Wege zu erreichen, dann nur her damit...

Gruß,

Christoph
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

OT:
BlackJack hat geschrieben: `ret` und einiges an Prüfcode könnte man einsparen wenn man am Ende ein ``return True`` schreibt und die Funktion verlässt mit ``return False`` verlässt, wenn eine der Bedingungen nicht erfüllt ist.
Nun, dazu kann ich nur sagen, daß die meisten Programmierrichtlinien, die mir bisher untergekommen sind, verlangen, daß es für eine Funktion / Methode nur *einen* Aussprungpunkt gibt. Man kann dazu stehen, wie man will, aber oft macht das den Code sicherer und besser lesbar.

:twisted: Wobei das mit der Lesbarkeit und Python in meinen Augen eh ein besonderes Thema ist, keine Klammerung, viele Operationen in einer Zeile und zwischen Klammer und dem nächsten Wort kein Leerzeichen und so... :twisted:

Aber das ist natürlich Geschmackssache und hängt auch direkt von den Fähigkeiten, den Code zu lesen und gleich zu verstehen, ab...

Gruß,

Christoph
ChrisGTJ
User
Beiträge: 105
Registriert: Mittwoch 22. August 2007, 15:45

Oh ja, dann noch eine Frage:

Warum

Code: Alles auswählen

getmro(motor.__class__)
anstelle von

Code: Alles auswählen

motor.__class__.__mro__
?
Benutzeravatar
BlackVivi
User
Beiträge: 762
Registriert: Samstag 9. Dezember 2006, 14:29
Kontaktdaten:

Der Übersichtlichkeit halber... oder schreibst du sowas oO:

Code: Alles auswählen

foo = "bar"
foo.__len__()
anstelle von

Code: Alles auswählen

foo = "bar"
len(foo)
Antworten