V rednem delu teh predavanj se igramo z raznimi čudnimi stvarmi, da bi pokazali razliko med imenom reči in rečjo samo. Isti stvari smo dali dve imeni in videli, da se, če jo spreminjamo prek enega imena, seveda spreminja tudi tisto, kar vidimo pod drugim imenom (to je, drugim imenom za isto reč). Če je neobrit Janez, je neobrit tudi Demšar.
Povedali smo tudi, kaj so v resnici argumenti funkcij: vedejo se kot lokalna imena za objekte, ki smo jih podali funkciji kot argument. Ničesar pa nismo povedali o tem, kje je vse to shranjeno.
Python shranjuje imena in vrednosti - kako priročno! - kar v
slovarju, ki si ga tiho sestavi v ta namen. Ključi tega slovarja so
imena, vrednosti pa pač objekti, na katere se ta imena nanašajo. Tega
slovarja na skriva: do njega pridemo, recimo, prek funkcije
globals()
.
globals()
{'__name__': '__main__',
'__doc__': 'Automatically created module for IPython interactive environment',
'__package__': None,
'__loader__': None,
'__spec__': None,
'__builtin__': <module 'builtins' (built-in)>,
'__builtins__': <module 'builtins' (built-in)>,
'_ih': ['', 'globals()'],
'_oh': {},
'_dh': ['/Users/janez/Desktop/predavanja/p1/dodatna predavanja'],
'In': ['', 'globals()'],
'Out': {},
'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f9da309b450>>,
'exit': <IPython.core.autocall.ZMQExitAutocall at 0x7f9da3f937d0>,
'quit': <IPython.core.autocall.ZMQExitAutocall at 0x7f9da3f937d0>,
'_': '',
'__': '',
'___': '',
'_i': '',
'_ii': '',
'_iii': '',
'_i1': 'globals()'}
globals()
nam torej vrne slovar z imeni in vrednostmi
spremenljivk. Ta že v začetku ni prazen. Nekaj te šare vedno naredi
Python, še veliko več pa je doda Jupyter Notebook, v katerem vse tole
predvajamo. Da ne gledamo vsega tega, si zapomnimo, kaj imamo tu in
poslej bomo izpisovali samo, kar se pojavi na novo.
= set(globals()) | {"stuff"} stuff
Sestavimo dve spremenljivki in izpišimo vsebino
globals()
(brez stuff
in vsega, kar se začne s
_
, saj Jupyter dodaja vedno več in več tega).
= 12
x = "Benjamin"
ime
for k, v in globals().items() if k not in stuff and k[0] != "_"} {k: v
{'x': 12, 'ime': 'Benjamin'}
Ko napišemo x = 12
, se v slovarju pojavi nov element;
ključ je niz "x"
, vrednost pa 12. Podobno se zgodi, ko
napišemo ime = "Benjamin"
.
Recimo, da zdaj pa vtipkajmo x + 7
. Python kot vemo,
najprej preveri, ali obstaja spremenljivka z imenom x
. To
stori preprosto tako, da pogleda v tale slovar. Če obstaja ključ
x
, v izrazu x + 7
uporabi pripadajočo
vrednost. Če ga ni, javi napako.
Napišem lahko tudi del x
, kar pobriše spremenljivko
(ups, ime!) x
. O tem se nismo posebej pogovarjali, ker ni
bilo potrebno. Če bi se pogovarjali o tem, pa bi vam lahko zdajle
povedal, da del x
ne naredi nič drugega, kot da pobriše
dotični ključ (in, seveda, pripadajočo vrednost) iz slovarja. In v tem
primeru bi to tudi pokazal.
del x
for k, v in globals().items() if k not in stuff and k[0] != "_"} {k: v
{'ime': 'Benjamin'}
= globals()
spremenljivke
for k, v in spremenljivke.items() if k not in stuff and k[0] != "_" and k != "spremenljivke"} {k: v
{'ime': 'Benjamin'}
Zdaj se ime spremenljivke
nanaša na slovar, ki ga vrne
globals()
. Ta slovar je slovar spremenljivk in zato, he he,
vsebuje tudi ključ spremenljivke
, pripadajoča vrednost pa
je kar ta slovar sam. Vendar ga raje nismo izpisali, ker bi se izpisala
tudi vsa Jupytrova šara, zato smo dodali še
k != spremenljivke
).
Če je spremenljivke
torej v resnici slovar spremenljivk
- lahko vanj dodajamo nove spremenljivke?
"foo"] = 42
spremenljivke["bar"] = [1, 2, 3] spremenljivke[
Pa preverimo.
foo
42
bar
[1, 2, 3]
Če torej napišemo foo = 42
(kot bi počeli normalno) je
to samo bližnjica za globals()["a"] = 42
(če smo natančni,
bližnjica za globals().__setitem__("a", 42)
, ampak tako
daleč v drobovje Pythona ne bomo rinili).
Podobno je x + 7
samo bližnjica za
globals()["x"] + 7
. V resnici se prvo “prevede” v drugo
(ali, če smo natančni, v nekaj podobnega drugemu).
Kar smo pravkar videli, ni posebej uporabno. Tu je, kar pač Python tega ne skriva. Ni pa mišljeno, da bi se to uporabljalo. Tudi mi smo pogledali samo zato, da smo videli, kako Python v resnici deluje. To nam bo namreč pomagalo razumeti, kar sledi.
Vemo, da ima vsaka funkcija svoje spremenljivke, svoja imena. Kako to
deluje, najbrž slutimo: vsaka ima svoj slovar. Do njega ne pridemo prek
globals()
temveč prek locals()
.
def f(x, y):
= 13
z print(locals())
1, "Benjamin") f(
{'x': 1, 'y': 'Benjamin', 'z': 13}
Lokalne spremenljivke, ki jih pozna f
, so torej
x
in y
, ki ju je dobila kot argument, in
z
, ki jo je ustvarila kasneje.
Poleg tega imajo funkcije seveda tudi dostop do globalnih
spremenljivk. Predstavljamo si, da pogledajo v oba slovarja, najprej v
locals
, nato še v globals
.
Predstavljamo si lahko? Da. Predstavljamo. Slovar
globals()
je resničen, locals()
je fake.
Python ga sestavi, če ravno prosimo zanj. Če ga spreminjamo, pa Python
ne bo v resnici dodajal spremenljivk. Lokalne spremenljivke funkcije
niso shranjene v slovarju, temveč na fiksnih lokacijah na skladu. Tule
(še) ne bomo šli pregloboko v to, kako delujejo funkcije, le nakažimo,
za kaj gre.
Ko takole kramljam s Pythonom in si izmišljam spremenljivke, recimo
seznam imena
, ki ga vedno znova definiram na predavanjih,
Python nikoli ne ve, katero spremenljivko si bom izmislil v naslednjem
trenutku. Ker pač to počnem sproti. S funkcijami pa ni tako. Tam si
spremenljivk ne izmišljam sproti. No, seveda, ko programiram, jih seveda
sestavljam sproti, med tipkanjem, brez deklariranja. Vendar Python
takrat funkcije še ne izvaja. Preden jo pokličem, pa Python že vidi vso
funkcijo (in jo v resnici celo prevede, čisto tako kot Java v JVM, C# v
CLI in Javascript v nimam pojma kaj). Takrat ve tudi za vse njene
spremenljivke; ker ne bo nihče spreminjal kode funkcije, tudi nihče ne
bo več dodajal spremenljivk. (Če bo kdo spreminjal funkcijo, pa se bo
itak ponovno prevedla.)
Funkcija zato "ve", koliko spremenljivk ima in kakšna so njihova
imena. Gornja funkcija f
ima tri spremenljivke in njihova
imena so x
, y
in z
- shranjena v
terkah, da jih lahko vidimo.
f.__code__.co_nlocals
3
f.__code__.co_varnames
('x', 'y', 'z')
Zakaj tako? Ker je hitrejše. Veliko hitrejše. Zato so v Pythonu lokalne spremenljivke hitrejše od globalnih, ker ne zahtevajo (sicer hitrega) pogleda v slovar.
Kot zanimivost: tudi katere konstante bo potrebovala funkcija, vemo
vnaprej, zato ni potrebno, da bi 13
v gornji funkciji
ustvarili v vsakem klicu posebej. Pripravi jo že prevajalnik, skupaj s
konstantno None
, ki jo funkcija vrne kot rezultat.
f.__code__.co_consts
(None, 13)
Nimam pojma. Ker je zanimivo?
Kje so shranjene spremenljivke modulov?
Najbrž v nekem slovarju. Globalnem ali lokalnem? V globalnem - očitno ne. V lokalnem? Ta pa ne obstaja. Vse spremenljivke funkcije so na skladu.
Polovico zgodbe vidimo tu:
import math
math.__dict__
{'__name__': 'math',
'__doc__': 'This module provides access to the mathematical functions\ndefined by the C standard.',
'__package__': '',
'__loader__': <_frozen_importlib_external.ExtensionFileLoader at 0x7f9da1c89290>,
'__spec__': ModuleSpec(name='math', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x7f9da1c89290>, origin='/Users/janez/miniconda3/envs/prog/lib/python3.7/lib-dynload/math.cpython-37m-darwin.so'),
'acos': <function math.acos(x, /)>,
'acosh': <function math.acosh(x, /)>,
'asin': <function math.asin(x, /)>,
'asinh': <function math.asinh(x, /)>,
'atan': <function math.atan(x, /)>,
'atan2': <function math.atan2(y, x, /)>,
'atanh': <function math.atanh(x, /)>,
'ceil': <function math.ceil(x, /)>,
'copysign': <function math.copysign(x, y, /)>,
'cos': <function math.cos(x, /)>,
'cosh': <function math.cosh(x, /)>,
'degrees': <function math.degrees(x, /)>,
'erf': <function math.erf(x, /)>,
'erfc': <function math.erfc(x, /)>,
'exp': <function math.exp(x, /)>,
'expm1': <function math.expm1(x, /)>,
'fabs': <function math.fabs(x, /)>,
'factorial': <function math.factorial(x, /)>,
'floor': <function math.floor(x, /)>,
'fmod': <function math.fmod(x, y, /)>,
'frexp': <function math.frexp(x, /)>,
'fsum': <function math.fsum(seq, /)>,
'gamma': <function math.gamma(x, /)>,
'gcd': <function math.gcd(x, y, /)>,
'hypot': <function math.hypot(x, y, /)>,
'isclose': <function math.isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)>,
'isfinite': <function math.isfinite(x, /)>,
'isinf': <function math.isinf(x, /)>,
'isnan': <function math.isnan(x, /)>,
'ldexp': <function math.ldexp(x, i, /)>,
'lgamma': <function math.lgamma(x, /)>,
'log': <function math.log>,
'log1p': <function math.log1p(x, /)>,
'log10': <function math.log10(x, /)>,
'log2': <function math.log2(x, /)>,
'modf': <function math.modf(x, /)>,
'pow': <function math.pow(x, y, /)>,
'radians': <function math.radians(x, /)>,
'remainder': <function math.remainder(x, y, /)>,
'sin': <function math.sin(x, /)>,
'sinh': <function math.sinh(x, /)>,
'sqrt': <function math.sqrt(x, /)>,
'tan': <function math.tan(x, /)>,
'tanh': <function math.tanh(x, /)>,
'trunc': <function math.trunc(x, /)>,
'pi': 3.141592653589793,
'e': 2.718281828459045,
'tau': 6.283185307179586,
'inf': inf,
'nan': nan,
'__file__': '/Users/janez/miniconda3/envs/prog/lib/python3.7/lib-dynload/math.cpython-37m-darwin.so'}
Vse, kar je v modulu, je shranjeno v modulovem slovarju
__dict__
. Pripravil sem modul primer.py
, ki je
videti tako.
= 15
x
def f(y):
print(globals())
return x * y
def g():
return f(12)
Uvozimo ga in preverimo, kaj je v njegovem __dict__
.
import primer
# __dict__ vsebuje __builtins__, ki vsebuje `print` in `globals` in druge funkcije
# Ker jih je veliko, jih nočemo izpisati: naredimo kopijo slovarja, pobrišimo
# `__builtins__ in ga izpišimo
= primer.__dict__.copy()
primdict del primdict["__builtins__"]
primdict
{'__name__': 'primer',
'__doc__': None,
'__package__': '',
'__loader__': <_frozen_importlib_external.SourceFileLoader at 0x7f9da58b2e10>,
'__spec__': ModuleSpec(name='primer', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7f9da58b2e10>, origin='/Users/janez/Desktop/predavanja/p1/dodatna predavanja/primer.py'),
'__file__': '/Users/janez/Desktop/predavanja/p1/dodatna predavanja/primer.py',
'__cached__': '/Users/janez/Desktop/predavanja/p1/dodatna predavanja/__pycache__/primer.cpython-37.pyc',
'x': 15,
'f': <function primer.f(y)>,
'g': <function primer.g()>}
Po pričakovanjih.
A z vidika funkcije f
je x
očitno globalna
spremenljivka. Preverimo, če je res - pokličimo funkcijo f
,
pa nam bo izpisala globalne spremenljivke.
12) primer.f(
{'__name__': 'primer', '__doc__': None, '__package__': '', '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f9da58b2e10>, '__spec__': ModuleSpec(name='primer', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7f9da58b2e10>, origin='/Users/janez/Desktop/predavanja/p1/dodatna predavanja/primer.py'), '__file__': '/Users/janez/Desktop/predavanja/p1/dodatna predavanja/primer.py', '__cached__': '/Users/janez/Desktop/predavanja/p1/dodatna predavanja/__pycache__/primer.cpython-37.pyc', '__builtins__': {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <bound method Kernel.raw_input of <ipykernel.ipkernel.IPythonKernel object at 0x7f9da3f750d0>>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'len': <built-in function len>, 'locals': <built-in function locals>, 'max': <built-in function max>, 'min': <built-in function min>, 'next': <built-in function next>, 'oct': <built-in function oct>, 'ord': <built-in function ord>, 'pow': <built-in function pow>, 'print': <built-in function print>, 'repr': <built-in function repr>, 'round': <built-in function round>, 'setattr': <built-in function setattr>, 'sorted': <built-in function sorted>, 'sum': <built-in function sum>, 'vars': <built-in function vars>, 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, 'complex': <class 'complex'>, 'dict': <class 'dict'>, 'enumerate': <class 'enumerate'>, 'filter': <class 'filter'>, 'float': <class 'float'>, 'frozenset': <class 'frozenset'>, 'property': <class 'property'>, 'int': <class 'int'>, 'list': <class 'list'>, 'map': <class 'map'>, 'object': <class 'object'>, 'range': <class 'range'>, 'reversed': <class 'reversed'>, 'set': <class 'set'>, 'slice': <class 'slice'>, 'staticmethod': <class 'staticmethod'>, 'str': <class 'str'>, 'super': <class 'super'>, 'tuple': <class 'tuple'>, 'type': <class 'type'>, 'zip': <class 'zip'>, '__debug__': True, 'BaseException': <class 'BaseException'>, 'Exception': <class 'Exception'>, 'TypeError': <class 'TypeError'>, 'StopAsyncIteration': <class 'StopAsyncIteration'>, 'StopIteration': <class 'StopIteration'>, 'GeneratorExit': <class 'GeneratorExit'>, 'SystemExit': <class 'SystemExit'>, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'ImportError': <class 'ImportError'>, 'ModuleNotFoundError': <class 'ModuleNotFoundError'>, 'OSError': <class 'OSError'>, 'EnvironmentError': <class 'OSError'>, 'IOError': <class 'OSError'>, 'EOFError': <class 'EOFError'>, 'RuntimeError': <class 'RuntimeError'>, 'RecursionError': <class 'RecursionError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'NameError': <class 'NameError'>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'AttributeError': <class 'AttributeError'>, 'SyntaxError': <class 'SyntaxError'>, 'IndentationError': <class 'IndentationError'>, 'TabError': <class 'TabError'>, 'LookupError': <class 'LookupError'>, 'IndexError': <class 'IndexError'>, 'KeyError': <class 'KeyError'>, 'ValueError': <class 'ValueError'>, 'UnicodeError': <class 'UnicodeError'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'AssertionError': <class 'AssertionError'>, 'ArithmeticError': <class 'ArithmeticError'>, 'FloatingPointError': <class 'FloatingPointError'>, 'OverflowError': <class 'OverflowError'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'SystemError': <class 'SystemError'>, 'ReferenceError': <class 'ReferenceError'>, 'MemoryError': <class 'MemoryError'>, 'BufferError': <class 'BufferError'>, 'Warning': <class 'Warning'>, 'UserWarning': <class 'UserWarning'>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'ImportWarning': <class 'ImportWarning'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'BytesWarning': <class 'BytesWarning'>, 'ResourceWarning': <class 'ResourceWarning'>, 'ConnectionError': <class 'ConnectionError'>, 'BlockingIOError': <class 'BlockingIOError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'ConnectionResetError': <class 'ConnectionResetError'>, 'FileExistsError': <class 'FileExistsError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'IsADirectoryError': <class 'IsADirectoryError'>, 'NotADirectoryError': <class 'NotADirectoryError'>, 'InterruptedError': <class 'InterruptedError'>, 'PermissionError': <class 'PermissionError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'TimeoutError': <class 'TimeoutError'>, 'open': <built-in function open>, 'copyright': Copyright (c) 2001-2019 Python Software Foundation.
All Rights Reserved.
Copyright (c) 2000 BeOpen.com.
All Rights Reserved.
Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.
Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object., '__IPYTHON__': True, 'display': <function display at 0x7f9da2920b00>, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f9da309b450>>}, 'x': 15, 'f': <function f at 0x7f9da58d50e0>, 'g': <function g at 0x7f9da58d78c0>}
180
Izpisalo se je še vse živo (vsebina tistega
__builtins__
). Bistveno je na koncu: x
,
f
in g
. Če modul primer
vpraša po
globals()
, dobi nekaj drugega, kot če po
globals()
vprašamo v svojem programu. Vsak modul ima svoj
globals()
.
Tako mora biti. Funkcija g
kliče funkcijo
f
. Pokliče jo kot f
, ne primer.f
.
Zato mora biti med globalnimi spremenljivkami. Če bi hotela funkcija
g
klicati primer.f
, potem bi morala prej
uvoziti modul primer
, torej samega sebe. To se sicer da,
vendar se lahko konča slabo, če ne pazimo, kako to storimo. Poleg tega
tega nikoli ne počnemo, ker ni smiselno.
import
in
import from
Zgoraj smo napisali import math
. Modul math
se pojavi med globalnimi spremenljivkami.
globals()["math"]
<module 'math' from '/Users/janez/miniconda3/envs/prog/lib/python3.7/lib-dynload/math.cpython-37m-darwin.so'>
Če namesto tega napišemo from math import cos
, se med
globalnimi spremenljivkami pojavi cos
.
from math import cos
globals()["cos"]
<function math.cos(x, /)>
__name__
Ena od globalnih spremenljivk, ki se je pojavila sama od sebe, je
__name__
. Ta je imela doslej vrednost
__main__
.
__name__
'__main__'
Poglejte, kakšno vrednost ima v globalnih spremenljivkah modula
primer
:
__name__ primer.
'primer'
Pod takim imenom se pojavi tudi v modulu primer
, če bi
ga, recimo, izpisali ali preverjali v primer
. Na ta način
lahko program ve, ali ga izvajamo kot program ali kot modul.
Na koncu testov, ki jih dobivate ob domačih nalogah, vedno pišem
if __name__ == "__main__":
unittest.main()
To požene teste, vendar le, če program poženemo kot običajen program.
Če pa PyCharm prepozna, da so v njem testi, ga bo uvozil kot modul, da
bo potem lahko dostopal do razredov s testi. V tem primeru
__name__
ne bo enak __main__
, temveč imenu
datoteke in funkcija unittest.main()
se ne bo poklicala.
Tako mora biti, da bo teste poklical PyCharm.
Če bi že poznali razrede, bi nas zanimalo, kje so shranjeni atributi.
Prav tako v slovarju in ime mu je, tako kot pri modulih,
__dict__
.
class A:
def __init__(self):
self.x = 42
def metoda(self):
return "Benjamin"
= A()
a a.x
42
a.__dict__
{'x': 42}
= 13
a.foo
a.__dict__
{'x': 42, 'foo': 13}
"bar"] = "Cilka"
a.__dict__[
a.bar
'Cilka'
Vse po pričakovanjih.
Kaj pa to?
a.metoda()
'Benjamin'
Odkod metoda
, če je vendar ni v a.__dict__
?
Ta je pa tu.
A.__dict__
mappingproxy({'__module__': '__main__',
'__init__': <function __main__.A.__init__(self)>,
'metoda': <function __main__.A.metoda(self)>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>,
'__doc__': None})
A s tem, kako razredi iščejo metode in atribute se bomo raje ukvarjali, ko pridemo do razredov.