NB_01_Namen,_Objekte_und_Namensräume

(c) 2019/2020 Hochschule Augsburg - Fakultät für Informatik - Prof.Dr.Nik Klever

Namen und Objekte

Grundlegendes

Objekte sind individuell und mehrere Namen (in unterschiedlichen Gültigkeitsbereichen) können an das selbe Objekt gebunden sein. Dieses Verhalten wird in anderen Programmiersprachen oft als Aliasing bezeichnet. Dies wird normalerweise in Python nicht auf den ersten Blick gewürdigt und kann sicherlich bei der Verwendung der unveränderlichen grundlegenden Datentypen (Zahlen, Zeichenketten, Tupel) ignoriert werden. Nichtsdestotrotz hat Aliasing einen möglicherweise überraschenden Effekt auf die Semantik von Python Code wenn es um veränderliche Datentypen wie Listen, Dictionaries und viele andere Datentypen geht. Dies wirkt sich normalerweise zugunsten der Programmierung aus, da Aliase sich in mancherlei Hinsicht wie Zeiger verhalten. Zum Beispiel ist es einfacher, einen Zeiger auf ein Objekt an eine Funktion zu übergeben als das ganze Objekt. Und falls die Funktion dieses Objekt modifiziert wird das aufrufende Programm diese Änderung sofort mitbekommen. Dieses Verhalten eleminiert die Notwendigkeit für unterschiedliche Aufruf-Mechanismen für Argumente von Funktionen, wie es in anderen Programmiersprachen notwendig ist.

Namens-Gültigkeitsbereich (Scope) und Namensräume (Namespaces)

Namensraum

Ein Namensraum (namespace) ist eine Abbbildung von Namen auf Objekte. Die meisten Namensräume in Python sind derzeit als Dictionaries implementiert, aber dies ist eigentlich nicht erwähnenswert (ausgenommen vielleicht bezgl. der Schnelligkeit) - und vorallem könnte sich dies in Zukunft auch ändern.

Das Wichtigste an Namensräumen ist, dass zwischen den Namen aus unterschiedlichen Namensräumen absolut keine Beziehung besteht. Zum Beispiel könnten zwei unterschiedliche Module (Bibliotheken) (z.B. eins und zwei) jeweils eine Funktion maximum definieren, ohne dass sich die beiden Funktionen gegenseitig beeinflussen würden. Der Grund hierfür liegt daran, dass diese beiden unterschiedlichen Funktionen nur mit dem entsprechenden vorangestellten Modul-Namen angesprochen und aufgerufen werden können (d.h. über eins.maximum und zwei.maximum, womit beide wieder unterschiedlich sind).

Beispiele von Namensräumen

Beispiele von Namensräumen sind:

  • die Menge der Builtin-Namen (die Funktionen wie abs(), open oder zip() oder auch die Builtin-Ausnahmen wie KeyError, NameError oder TypeError beinhalten)
  • die globalen Namen in einem Modul
  • und die lokalen Namen in einem Funktions-Aufruf
  • in gewisser Hinsicht bildet auch die Menge der Attribute eines Objektes ebenfalls einen Namensraum

Namensräume werden zu verschiedenen Zeitpunkten erstellt und haben unterschiedliche Lebensdauer:

  • Der Namensraum mit den builtin-Namen wird erstellt, wenn der Python-Interpreter gestartet wird und er wird nie gelöscht.
  • Der globale Namensraum eines Moduls wird beim Einlesen der Moduldefinition erstellt; Normalerweise bleiben Modul-Namensräume bestehen, bis der Python-Interpreter beendet wird.
  • Die Anweisungen, die durch den Aufruf des Python-Interpreters auf oberster Ebene ausgeführt werden, die entweder aus einer Skriptdatei gelesen oder interaktiv eingegeben werden, werden als Teil eines Moduls mit dem Namen __main__ betrachtet, so dass diese über einen eigenen globalen Namensraum verfügen.
  • Die builtin-Namen sind eigentlich auch in einem eigenen Modul namens builtins zusammengefasst.

Builtins

Die folgenden Anweisungen werden nicht im Jupyter Notebook ausgeführt sondern in einem direkten Aufruf des Python-Interpreters. Der Grund liegt darin, dass in einem Jupyter Notebook sehr viele Variable enthalten sind, die zum Verständnis der Namensräume eher noch mehr verwirren.

In [2]:
!python -c "print(dir())"
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
In [3]:
dir(__builtins__)
Out[3]:
['ArithmeticError',
 'AssertionError',
 'AttributeError',
 'BaseException',
 'BlockingIOError',
 'BrokenPipeError',
 'BufferError',
 'BytesWarning',
 'ChildProcessError',
 'ConnectionAbortedError',
 'ConnectionError',
 'ConnectionRefusedError',
 'ConnectionResetError',
 'DeprecationWarning',
 'EOFError',
 'Ellipsis',
 'EnvironmentError',
 'Exception',
 'False',
 'FileExistsError',
 'FileNotFoundError',
 'FloatingPointError',
 'FutureWarning',
 'GeneratorExit',
 'IOError',
 'ImportError',
 'ImportWarning',
 'IndentationError',
 'IndexError',
 'InterruptedError',
 'IsADirectoryError',
 'KeyError',
 'KeyboardInterrupt',
 'LookupError',
 'MemoryError',
 'ModuleNotFoundError',
 'NameError',
 'None',
 'NotADirectoryError',
 'NotImplemented',
 'NotImplementedError',
 'OSError',
 'OverflowError',
 'PendingDeprecationWarning',
 'PermissionError',
 'ProcessLookupError',
 'RecursionError',
 'ReferenceError',
 'ResourceWarning',
 'RuntimeError',
 'RuntimeWarning',
 'StopAsyncIteration',
 'StopIteration',
 'SyntaxError',
 'SyntaxWarning',
 'SystemError',
 'SystemExit',
 'TabError',
 'TimeoutError',
 'True',
 'TypeError',
 'UnboundLocalError',
 'UnicodeDecodeError',
 'UnicodeEncodeError',
 'UnicodeError',
 'UnicodeTranslateError',
 'UnicodeWarning',
 'UserWarning',
 'ValueError',
 'Warning',
 'ZeroDivisionError',
 '__IPYTHON__',
 '__build_class__',
 '__debug__',
 '__doc__',
 '__import__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'abs',
 'all',
 'any',
 'ascii',
 'bin',
 'bool',
 'bytearray',
 'bytes',
 'callable',
 'chr',
 'classmethod',
 'compile',
 'complex',
 'copyright',
 'credits',
 'delattr',
 'dict',
 'dir',
 'display',
 'divmod',
 'enumerate',
 'eval',
 'exec',
 'filter',
 'float',
 'format',
 'frozenset',
 'get_ipython',
 'getattr',
 'globals',
 'hasattr',
 'hash',
 'help',
 'hex',
 'id',
 'input',
 'int',
 'isinstance',
 'issubclass',
 'iter',
 'len',
 'license',
 'list',
 'locals',
 'map',
 'max',
 'memoryview',
 'min',
 'next',
 'object',
 'oct',
 'open',
 'ord',
 'pow',
 'print',
 'property',
 'range',
 'repr',
 'reversed',
 'round',
 'set',
 'setattr',
 'slice',
 'sorted',
 'staticmethod',
 'str',
 'sum',
 'super',
 'tuple',
 'type',
 'vars',
 'zip']

Globals

In [4]:
!python -c "print(globals())"
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
In [5]:
!python -c "print(dir())"
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
In [6]:
!python -c "a=3.141;b=2;c='Text';d=['a','b','c'];e={'a':1,'b':2,'c':3};print(dir())"
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b', 'c', 'd', 'e']
In [7]:
!python -c "a=3.141;b=2;c='Text';d=['a','b','c'];e={'a':1,'b':2,'c':3};print(globals())"
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': 3.141, 'b': 2, 'c': 'Text', 'd': ['a', 'b', 'c'], 'e': {'a': 1, 'b': 2, 'c': 3}}
In [8]:
!python -c "a=3.141;b=2;c='Text';d=['a','b','c'];e={'a':1,'b':2,'c':3};print({x:globals()[x] for x in globals() if not x.startswith('__')})"
{'a': 3.141, 'b': 2, 'c': 'Text', 'd': ['a', 'b', 'c'], 'e': {'a': 1, 'b': 2, 'c': 3}}

Modul-Namensraum und Attribute

In der obigen Auflistung wird von Attributen gesprochen. Attibute sind generell Namen, die nach einem Punkt stehen. Zum Beispiel ist in dem Ausdruck z.real real ein Attribut des Objektes z. Streng genommen sind Verweise auf Namen in Modulen Verweise auf Attribute: in dem Ausdruck modulname.functionname ist modulename ein Modulobjekt und functionname ist ein Attribut von diesem Modulobjekt. In diesem Fall gibt es eine direkte Abbildung zwischen den Attributen des Moduls und den globalen Namen des Moduls: beide teilen sich den gleichen Namensraum (siehe oben builtins)

Attribute können nur lesbar oder les- und schreibbar sein. Im letzteren Fall ist die Zuweisung an die Attribute möglich. Modulattribute sind schreibbar: modulname.variableZahl = 42 weist die Zahl 42 dem Modulattribut variableZahl zu. Schreibbare Attribute können auch mit dem del-Ausdruck gelöscht werden. Zum Beispiel wird del modulname.variableZahl das Attribut variableZahl aus dem mit modulname benannten Objekt entfernen.

In [9]:
__builtins__.abs == abs
Out[9]:
True
In [10]:
__builtins__.KeyError == KeyError
Out[10]:
True
In [11]:
import sys
print(sys.version)
del sys 
3.6.4 |Anaconda custom (64-bit)| (default, Mar 13 2018, 01:15:57) 
[GCC 7.2.0]
In [12]:
!python -c "import sys;print(sys.version);del sys.version;print(sys.version)"
3.6.4 |Anaconda custom (64-bit)| (default, Mar 13 2018, 01:15:57) 
[GCC 7.2.0]
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: module 'sys' has no attribute 'version'

Lokaler Namensraum

Der lokale Namensraum für eine Funktion wird erstellt, wenn die Funktion aufgerufen wird und er wird gelöscht, wenn die Funktion normal beendet wird oder die Funktion eine Ausnahme wirft, die nicht innerhalb der Funktion abgefangen wird. (Eigentlich wäre der Begriff vergessen statt löschen ein besserer Ausdruck, um zu beschreiben, was tatsächlich passiert.) Rekursive Aufrufe einer Funktion besitzen jeweils einen eigenen lokalen Namensraum.

Gültigkeitsbereich

Der Gültigkeitsbereich (scope) ist eine textuelle Region eines Python-Programms, auf welche die Namen aus einem Namensraum direkt zugreifbar sind. "Direkt zugreifbar" bedeutet hier, dass der Python-Interpreter versucht, einen unqualifizierten Verweis auf einen Namen (d.h. grob gesprochen, dass der Name keinen Punkt enthält) in eben diesem Namensraum zu finden.

Obwohl Gültigkeitsbereiche statisch bestimmt werden, werden sie dynamisch verwendet. Zu jeder Zeit während der Ausführung eines Python-Programms gibt es mindestens drei ineinander verschachtelte Gültigkeitsbereiche, deren Namensräume direkt zugänglich sind:

  • Der innerste Gültigkeitsbereich, der zuerst durchsucht wird, enthält die lokalen Namen
  • Die Gültigkeitsbereiche von allen umschließenden Funktionen, die mit der Suche in dem Gültigkeitsbereich der nächsten umschließenden Funktion begonnen wird und sowohl nicht-lokale als auch nicht-globale Namen enthält
  • Der vorletzte Gültigkeitsbereich enthält die globalen Namen des aktuellen Moduls
  • Der äußerste Gültigkeitsbereich, der zuletzt durchsucht wird, ist der Builtin-Namensraum, der die builtin-Namen enthält.

Falls ein Name global deklariert wird, gehen alle Referenzen und Zuweisungen direkt auf den mittleren Gültigkeitsbereich über, der die globalen Namen des Moduls enthält. Um Variablen anzusprechen, die außerhalb des innersten Gültigkeitsbereichs definiert sind, kann die Anweisung nonlocal verwendet werden; Wenn die entsprechenden Variablen nicht nonlocal deklariert werden, sind diese Variablen nur lesbar (ein Versuch, eine solche Variable zu beschreiben, erzeugt einfach eine neue lokale Variable im innersten Bereich, wobei die identisch benannte äußere Variable unverändert bleibt).

Normalerweise verweist der lokale Bereich auf die lokalen Namen der (textlich) aktuellen Funktion. Außerhalb von Funktionen verweist der lokale Bereich auf denselben Namespace wie der globale Bereich: der Namensraum des Moduls. Klassendefinitionen definieren einen weiteren Namensraum in dem lokalen Bereich.

Zusätzliche Informationen

Es ist wichtig zu erkennen, dass Gültigkeitsbereiche in textueller Form bestimmt werden: Der globale Gültigkeitsbereich einer in einem Modul definierten Funktion ist der Namensraum des Moduls, egal von wo oder von welchem Aliasnamen die Funktion aufgerufen wird. Auf der anderen Seite wird die eigentliche Suche nach Namen dynamisch, d.h. zur Laufzeit durchgeführt - jedoch entwickelt sich die Sprachdefinition in Richtung statischer Namensauflösung, d.h. zur compile-Zeit, was bedeutet, dass man sich nicht auf die dynamische Namensauflösung verlassen sollte! (In der Tat sind lokale Variablen derzeit bereits statisch bestimmt.)

Eine Besonderheit von Python ist, dass - wenn keine global Anweisung vorhanden ist - die Zuweisungen zu Namen immer in den innersten Gültigkeitsbereich gehen. Zuweisungen kopieren keine Daten - sie binden nur Namen an Objekte. Gleiches gilt für das Löschen von Variablen: Die Anweisung del x entfernt die Bindung von x aus dem Namensraum, auf den sich der lokale Gültigkeitsbereich bezieht. Tatsächlich verwenden alle Operationen, die neue Namen einführen, den lokalen Gültigkeitsbereich: Insbesondere importieren Anweisungen und Funktionsdefinitionen den Modul- oder Funktionsnamen in den lokalen Gültigkeitsbereich.

Die Anweisung global kann verwendet werden, um anzuzeigen, dass bestimmte Variablen in dem globalen Gültigkeitsbereich leben und dort auch gebunden sein sollten; Die Anweisung nonlocal gibt an, dass bestimmte Variablen in dem Gültigkeitsbereich einer umschliessenden Funktion leben und dort auch gebunden sein sollten.

Kurze Zusammenfassung

Mit der Anweisung nonlocal und einer anschliessenden Auflistung von Variablen werden in einer Funktion die entsprechenden Variablen aus dem Gültigkeitsbereich der nächsthöheren Funktion bekannt gemacht und beschreibbar. Analog gilt dies für die Anweisung global, mit der die anschliessend aufgelisteten Variablen im globalen Gültigkeitsbereich bekannt gemacht und beschreibbar werden. Fallse diese Anweisungen fehlen, werden jeweils neue Variable des gleichen Namens erstellt und verwendet, sodass die übergeordneten Variablen nicht angetastet werden.

Beispiele für Gültigkeitsbereiche mit Funktionen

Beispiel 1

Dieses Beispiel demonstriert die unterschiedlichen Gültigkeitsbereiche und Namensräume und wie die Anweisungen global und nonlocal die Bindung der Variablen an Namensräume verändert:

In [13]:
def test_scopes():
    def local_scope():
        spam = "local spam"
        print("innerhalb der Funktion local_scope:",spam)

    def nonlocal_scope():
        nonlocal spam
        spam = "nonlocal spam"
        print("innerhalb der Funktion nonlocal_scope:",spam)

    def global_scope():
        global spam
        spam = "global spam"
        print("innerhalb der Funktion global_scope:",spam)

    spam = "test spam"
    print("Vor dem Aufruf der Funktion local_scope:",spam)
    local_scope()
    print("Nach dem Aufruf der Funktion local_scope:", spam)
    nonlocal_scope()
    print("Nach dem Aufruf der Funktion nonlocal_scope:", spam)
    global_scope()
    print("Nach dem Aufruf der Funktion global_scope:", spam)

test_scopes()
print("Nach dem Aufruf von test_scopes im globalen Namensraum:", spam)
Vor dem Aufruf der Funktion local_scope: test spam
innerhalb der Funktion local_scope: local spam
Nach dem Aufruf der Funktion local_scope: test spam
innerhalb der Funktion nonlocal_scope: nonlocal spam
Nach dem Aufruf der Funktion nonlocal_scope: nonlocal spam
innerhalb der Funktion global_scope: global spam
Nach dem Aufruf der Funktion global_scope: nonlocal spam
Nach dem Aufruf von test_scopes im globalen Namensraum: global spam

Eine lokale Zuweisung (welche standardmäßig verwendet wird) verändert die Bindung der Variablen spam im Gültigkeitsbereich der Funktion test_scopes nicht verändert, im Gegensatz zur nonlocal Zuweisung, welche die Bindung der Variablen spam im Gültigkeitsbereich von test_scopes verändert, und die globale Zuweisung ändert die Bindung auf Modulebene. Zudem gibt es vor der globalen Zuweisung keine anfängliche Bindung für die Variable spam.

Beispiel 2

In [14]:
globalvars = set(dir())
print("Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):\n{} ".format([x for x in (set(dir())-globalvars-{'globalvars'}) if not x.startswith("_")]))

a=2
b=2
c=2

print("Werte von a,b,c im globalen Gültigkeitsbereich:\na={},b={},c={}".format(a,b,c))

def f(x,a=1,b=1):
    c = 1
    rs = a*x**2+b*x+c
    print("Namen im lokalen Gültigkeitsbereich der Funktion f:\n{}".format(dir()))
    print("Werte von a,b,c innerhalb der Funktion f:\na={},b={},c={}".format(a,b,c))
    return rs

def g(a,b):
    import math
    global c
    c = math.sqrt(a**2+b**2)
    print("Namen im lokalen Gültigkeitsbereich der Funktion g:\n{}".format(dir()))
    print("Werte von a,b,c innerhalb der Funktion g:\na={},b={},c={}".format(a,b,c))
    return c

def h(x):
    rs = f(x,a,b)
    print("Namen im lokalen Gültigkeitsbereich der Funktion h:\n{}".format(dir()))
    print("Werte von a,b,c innerhalb der Funktion h:\na={},b={},c={}".format(a,b,c))
    return rs
Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):
[] 
Werte von a,b,c im globalen Gültigkeitsbereich:
a=2,b=2,c=2
In [15]:
print("Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):\n{} ".format([x for x in (set(dir())-globalvars-{'globalvars'}) if not x.startswith("_")]))
print("Werte von a,b,c im globalen Gültigkeitsbereich:\na={},b={},c={}".format(a,b,c))
f(2)
Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):
['c', 'a', 'b', 'f', 'h', 'g'] 
Werte von a,b,c im globalen Gültigkeitsbereich:
a=2,b=2,c=2
Namen im lokalen Gültigkeitsbereich der Funktion f:
['a', 'b', 'c', 'rs', 'x']
Werte von a,b,c innerhalb der Funktion f:
a=1,b=1,c=1
Out[15]:
7
In [16]:
print("Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):\n{} ".format([x for x in (set(dir())-globalvars-{'globalvars'}) if not x.startswith("_")]))
print("Werte von a,b,c im globalen Gültigkeitsbereich:\na={},b={},c={}".format(a,b,c))
g(3,4)
Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):
['c', 'a', 'b', 'f', 'h', 'g'] 
Werte von a,b,c im globalen Gültigkeitsbereich:
a=2,b=2,c=2
Namen im lokalen Gültigkeitsbereich der Funktion g:
['a', 'b', 'math']
Werte von a,b,c innerhalb der Funktion g:
a=3,b=4,c=5.0
Out[16]:
5.0
In [17]:
print("Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):\n{} ".format([x for x in (set(dir())-globalvars-{'globalvars'}) if not x.startswith("_")]))
print("Werte von a,b,c im globalen Gültigkeitsbereich:\na={},b={},c={}".format(a,b,c))
h(2)
Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):
['c', 'a', 'b', 'f', 'h', 'g'] 
Werte von a,b,c im globalen Gültigkeitsbereich:
a=2,b=2,c=5.0
Namen im lokalen Gültigkeitsbereich der Funktion f:
['a', 'b', 'c', 'rs', 'x']
Werte von a,b,c innerhalb der Funktion f:
a=2,b=2,c=1
Namen im lokalen Gültigkeitsbereich der Funktion h:
['rs', 'x']
Werte von a,b,c innerhalb der Funktion h:
a=2,b=2,c=5.0
Out[17]:
13
In [18]:
print("Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):\n{} ".format([x for x in (set(dir())-globalvars-{'globalvars'}) if not x.startswith("_")]))
print("Werte von a,b,c im globalen Gültigkeitsbereich:\na={},b={},c={}".format(a,b,c))
Namen im globalen Gültigkeitsbereich (ohne Jupyter-spezielle Variable):
['c', 'a', 'b', 'f', 'h', 'g'] 
Werte von a,b,c im globalen Gültigkeitsbereich:
a=2,b=2,c=5.0
In [19]:
%%Mooc StringAssesment
Out[19]:

Gültigkeitsbereich

Gegeben sei folgender Code:


a = 2
b = 4
c = 6
def test(x,fkt):
    a = 1
    b = 2
    c = 3
    if fkt == "f":
        def f(x):
            global a,b,c
            return a*x**2+b*x+c
        return f(x)
    elif fkt == "g":
        def g(x):
            nonlocal a,b,c
            a = 3
            b = 5
            c = 7
            return a*x**2+b*x+c
        return g(x)
    elif fkt == "h":
        def h(x):
            return a*x**2+b*x+c
        return h(x)

print(test(2,"f"),test(2,"g"),test(2,"h"))

Wie lautet die Ausgabe der Print-Funktion ?



In [20]:
%%Mooc Video
Out[20]:

Weitere Literatur

In [21]:
%%Mooc WebReference

A Word about Names and Objects

https://docs.python.org/3/tutorial/classes.html#a-word-about-names-and-objects

Hinweis: Mehr Informationen zu Namen und Objekten

In [22]:
%%Mooc WebReference

Python Scopes and Namespaces

https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces

Hinweis: Mehr Informationen zu Gültigkeitsbereichen und Namensräumen in Python