Ostatnio się bardzo zdziwiłem faktem jak działa importowanie w
Pythonie. Taki przykład:
module1.py
------------------------
class Test1(object):
my_var = 'a'
@staticmethod
def change():
Test1.my_var = 'b'
=============
module2.py
-----------------------
import module1
module1.Test1.change()
print module1.Test1.my_var
============
module3.py
----------------------
from module1 import Test1
print Test1.my_var
===========
Zakładając, że wszystko się wykonuje w jednym programie. Najpierw
module2 ustawia zmienną instancji klasy Test1 na wartość 'b'.
Tak więc w module3 spodziewałem się ujrzeć 'b', a jest 'a'.
Wygląda to tak, że from .... import rzeczywiście importuje do swojej
przestrzeni czyli hmm... tak jakby robił kopię obiektu.
Czytając wszelakie materiały nie widziałem (lub przegapiłem, lub nie
zrozumiałem) wzmiankę o takim zachowaniu.
Teraz pytanie, co zrobić, aby w module3 mieć dostęp do TEGO SAMEGO
obiektu Test1 co w module1 i w module2 ale nie musieć pisać za każdym
razem module1.Test1.my_var.
Pozdrawiam
Michał
> Najpierw module2 ustawia zmienną instancji klasy Test1 na wartość 'b'.
> Tak więc w module3 spodziewałem się ujrzeć 'b', a jest 'a'.
> Wygląda to tak, że from .... import rzeczywiście importuje do swojej
> przestrzeni czyli hmm... tak jakby robił kopię obiektu.
Nie. Import .. from .. jedynie dodaje do słownika odpowiednie nazwy,
które prowadzą do tych samych obiektów. Z tego co pamiętam, istnieje
sposób żeby python zaimportował to samo 2 razy, ale nie jest to aż
takie proste. Moduł ten musiałby być dostępny pod dwoma różnymi
nazwami.
Michal M napisał(a):
Twierdzisz, ze masz np. program `main.py` postaci:
import module2
import module3
i raz widzisz 'a' a raz 'b'?
>
> Teraz pytanie, co zrobić, aby w module3 mieć dostęp do TEGO SAMEGO
> obiektu Test1 co w module1 i w module2 ale nie musieć pisać za każdym
> razem module1.Test1.my_var.
To jest i musi byc ten sam obiekt, o czym mozesz sie latwo przekonac
wyswietlajac sobie wszedzie "id(Test1)".
RW
>
> Twierdzisz, ze masz np. program `main.py` postaci:
> import module2
> import module3
>
> i raz widzisz 'a' a raz 'b'?
>
Ja zrozumia�em �e robi tak:
1. Pisze program module1.py i tworzy tam jak�� zmienn�
2. Tworzy program module2.py w kt�rym importuje module1.py
3. W programie module2.py zmienia zmiennďż˝ w module1.py
4. W programie module3.py importuje module2.py i pr�buje odczyta�
zmienn�, kt�r� z modu�u module1.py i ma mie� tak� warto�� na jak�
zmieniďż˝ w module.2py
Importy w pythonie dzia�aj� podobnie do header�w w C++. To nie jest
rzeczywisty import rezyduj�cego gdzie� w pami�ci modu�u. To jest de
facto rozszerzenie przestrzeni nazw do kt�rej masz dost�p. Obiekt klasy
Test1 w module2.py jest zupe�nie innym obiektem ni� obiekt w module3.py
Nic dziwnego, �e masz inne warto�ci.
Aby zrobi� to co ty chcesz musia�by� si� zaciekawi� komunikacj�
mi�dzyprocesow�. Zajrzyj do modu�u multiprocessing
--
Pozdrawiam,
silver
>
> Twierdzisz, ze masz np. program `main.py` postaci:
> import module2
> import module3
>
> i raz widzisz 'a' a raz 'b'?
>
Ja zrozumia�em �e robi tak:
1. Pisze program module1.py i tworzy tam jak�� zmienn�
2. Tworzy program module2.py w kt�rym importuje module1.py
3. W programie module2.py zmienia zmiennďż˝ w module1.py
4. W programie module3.py importuje module2.py i pr�buje odczyta�
zmienn�, kt�r� zmieni� w module module1.py i ma mie� tak� warto�� na
from module1 import Lang
to widziałem 'a', a jak wstawiałem
import module1
to widziałem 'b'.
Dlatego moje zdziwienie, bo wyglądało jakby te dwa nie powodowały
tylko udostępnienia obiektu w przestrzeni nazw lokalnego modułu ale
importowały (kopiowały) cały obiekt do tego modułu i tworzyły
instancję. Wtedy obiekt Lang z module1 nie byłby tym samym co ten
zaimportowany w module3.
ALE... dzisiaj dziwnym trafem nie mogę odtworzyć tej sytuacji (ten sam
kod, nie zmieniany) i wszystko działa jak Wy i Ja przewidujecie. Wobec
tego wypada mi tylko przeprosić za zamieszanie i podziękować za
odpowiedzi.
I fajnie by było, ale OP napisał też:
> Zakładając, że wszystko się wykonuje w jednym programie.
No więc na moje oko dopasowałeś swoją wersję wyjaśnienia do błędu
opisanego przez użytkownika. ;-)
A mnie błąd użytkownika wygląda na (1) brak importu modułu 2, ew. (2)
za kulisami dzieje się coś, co sprawia, że jednak dostaje świeże
instancje Test1(). Python dość uparcie traktuje moduły jako singletony
i wydaje mi się, że przedstawiony przykład zbytnio spłyca obraz
sytuacji (a przede wszystkim nie oddaje opisanego zachowania -- jeśli
zaimportowany zostanie moduł 2 i moduł 3, to wartości są 'b', gdyż
moduł 1 jest ładowany jednokrotnie).
> --
> Pozdrawiam,
> silver
Nie piszesz przypadkiem pod Unixem ? Efekt jaki opisujesz mo�e by�
spowodowany sytuacjďż˝, gdy interpreter nie potrafi nadpisaďż˝ lub utworzyďż˝ .pyc
i ka�dy import powoduje parsowanie .py.
Nic podobnego. Kod i tak jest �adowany tylko raz.
RW
> Importy w pythonie działają podobnie do headerów w C++. To nie jest
> rzeczywisty import rezydującego gdzieś w pamięci modułu. To jest de
> facto rozszerzenie przestrzeni nazw do której masz dostęp. Obiekt klasy
> Test1 w module2.py jest zupełnie innym obiektem niż obiekt w module3.py
> Nic dziwnego, że masz inne wartości.
To nie tak. Modul jest jeden - w obrebie jednego programu - zaimportowanie
go wiele razy i tak zawsze zwroci ten sam obiekt, chyba ze sie zrobi
jakies sztuczki z __import__. Include w C i pochodnych dziala kompletnie
inaczej.
Radomir Dopieralski, http://sheep.art.pl
Bzdura.
> To nie jest
> rzeczywisty import rezyduj�cego gdzie� w pami�ci modu�u. To jest de
> facto rozszerzenie przestrzeni nazw do kt�rej masz dost�p. Obiekt klasy
> Test1 w module2.py jest zupe�nie innym obiektem ni� obiekt w module3.py
> Nic dziwnego, �e masz inne warto�ci.
>
> Aby zrobi� to co ty chcesz musia�by� si� zaciekawi� komunikacj�
> mi�dzyprocesow�.
Co???
> Zajrzyj do modu�u multiprocessing
>
Co ma piernik do wiatraka?
pzdr
\SK
--
"Never underestimate the power of human stupidity" -- L. Lang
--
http://www.tajga.org -- (some photos from my travels)
Co to znaczy tak jak w Javie?
Poka� ca�o�� -- co importuje co.
Czyli co i gdzie importuje module2 oraz module3.
[...]
> ALE... dzisiaj dziwnym trafem nie mogďż˝ odtworzyďż˝ tej sytuacji (ten sam
> kod, nie zmieniany) i wszystko dzia�a jak Wy i Ja przewidujecie. Wobec
> tego wypada mi tylko przeprosi� za zamieszanie i podzi�kowa� za
> odpowiedzi.
Mo�e jednak co� nie by�o tak samo. Np zapomnia�e� zrobi� "save" w
edytorze (to si� cz�sto zdarza)
module1.py
log = None
module2.py
class Log(object):
def __init__(self):
pass
def m(self):
print "ok"
module3.py
import module1
import module2
import module4
module1.log = module2.Log()
t = module4.Test()
t.use_m()
module4.py
from module1 import log
class Test(object):
def __init__(self):
pass
def use_m(self):
log.m()
Uruchamiam moduł 3. Ja to widzę tak:
1. importuje moduły 1, 2 i 3 (czyli udostępnia ich przestrzeń poprzez
nazwę modułu w module 3)
2. pod zmienną log w module 1 podstawia instancję klasy Log z modułu 2
3. Pod zmienną t podstawia instancję klasy Test z modułu 4 i próbuje
wykonać jej metodę use_m()
W tym miejscu otrzymuję błąd: AttributeError: 'NoneType' object has no
attribute 'm'
Teraz powiedzmy, że moduł 4 zmienię tak:
module4.py
import module1
class Test(object):
def __init__(self):
pass
def use_m(self):
module1.log.m()
Czyli wg mnie powinno być to samo, tyle że zamiast bezpośrednio do log
mam dostęp poprzez module1.log.
Zastanawiałem się, czy może samo log.m() nie widzi jako zmiennej
lokalnej i nie potrzeba dodać global log ale też nie pomaga.
Proszę o wyjaśnienie.
Pozdrawiam
Michał
Po wpisaniu takiego kodu
import module2
import module3
from module1 import Test1
print Test1.my_var
otrzymuje:
b
b
b
Wiec nie wiem w czym jest problem zwłaszcza tych bredni o nie
rzeczywistym importowaniu.
Python tak działa.
Zmienne można powiedzieć są etykietowane przez nazwę. Oznacza to że
jeżeli użyjemy instrukcji przypisania to przypiszemy danej etykiecie
nowy obiekt np.
log=None # obiekt None otrzymuje etykietę log
b=log # obiekt None otrzymuje nowa etykietę b
# teraz jeśli napiszę
log=3 # obiekt 3 otrzymuje etykietę log ale etykieta b nadal odnosi
sie do obiektu None (tak samo )
Pisząc
module1.log= ...
modyfikujesz tylko etykiete w przestrzeni nazw modułu modul1. tylko
importy wykonane po tej instrukcji będą widzieć ta zmianę.
Ale dosyć tych bredni.
Jeżeli chcesz zamiast module1.log.m() pisać log.m() zrezygnuj z
module2 i napisz w module1
class Log(object):
def __init__(self):
pass
def m(self):
print "ok"
log = None
potem piszesz w dowolmym module
from module1 import log
oczywiście jeśli napiszesz potem "gdzieś" module1.log=... to znów
"gdzieś" odnosić się bedziesz do innego obiektu.
i możesz z obiektu log pakować i doczytywać cokolwiek:
log.xxx=3
a winnym module
print log.xxx
Dzięki za odpowiedź. To nie brednie to pomaga mi zrozumieć to
zachowanie (choć jeszcze tego nie ogarniam). Mnie to po prostu
wyglądało, że log jest importowane do przestrzeni module4 i zmiana
przypisania do tej zmiennej w module 1 nie jest odzwierciedlana w
module4. Inaczej jest jeśli odwołuję się przez module1.log. Wtedy
odwołuję się do zmiennej właśnie w tamtej przestrzeni nazw (module1).
Nie wiem czy to wyjaśnienie jest zbieżne z tym co ty napisałeś (i
prawidłowe) ale tłumaczy to zachowanie i jest tym co chciałem na
początku przekazać w pierwszym poście.
Ze względu na to, że projekt będzie pewnie trochę większy, zrozumienie
tego ma dla mnie duże znaczenie. Dlatego też, nie chciałbym przenosić
log do modułu w którym jest Log bo chciałbym takie rzeczy trzymać
razem w jednym module aby się później nie pogubić (gdzie ja to
umieściłem.)
Jeszcze raz dziękuję za wyjaśnienie i pozdrawiam.
0) zaczynasz wykonywanie module3
1) import module1
1.1) do słownika modułu module1 dodawana jest nazwa "log" wskazująca
na obiekt None
2) import module2
2.1) wykonywane jest ciało klasy Log
2.2) do nazwy Log w module2 przypisywana jest nowo utworzona klasa Log
3) import module4
3.1) from module1 import log
3.1.1) moduł "module1" jest już zaimportowany, więc odczytywane jest
na jaki obiekt wskazuję nazwa "log" w jego słowniku (jest to None)
3.1.2) w słowniku module4 tworzona jest nowa nazwa i wskazuje ona na
None
3.2) Tworzona jest klasa Test (analogicznie do klasy Log)
4) module1.log = module2.Log() # nazwa log w słowniku module1
wskazuję teraz na nowy obiekt klasy Log. Słownik module4 jest
niezmieniony.
5) t = module4.Test()
6) t.use_m()
6.1) log nie jest lokalne dla use_m, więc jest globalne. Globalnym
kontekstem dla use_m() jest module4. module4.log wskazuję na None ->
błąd.
Czyli
from module1 import log
Tworzy nową zmienną (etykietkę jak wy to nazywacie) w przestrzeni
(słowniku) module4 i podstawia pod nią referencję (akurat None, bo się
jeszcze nie wykonało podstawienie instancji Log).
Natomiast
import module1
module1.log
Wskazuje na tą samą zmienną którą zmieniam w module1.
Innymi słowy, po
from module1 import log
mam dwie etykietki log i są one niezależne od siebie.
Muszę z tym uważać
Rozwiązaniem wygodnym byłoby zapisanie w module 4
import module1
log = module1.log
... gdyby nie to, że w każdej funkcji musiałbym dodać
global log
tak?
Wtedy gra nie warta świeczki.
No cóż... każdy język ma swoje udziwnienia. :)
Dziękuję wszystkim za wyjaśnienia.
Jeszcze takie pytanie. Gdybym zrobił tak:
module4.py
class Test(object):
def __init__(self):
from module1 import log
pass
def use_m(self):
log.m()
Czyli zaimportował de facto log po podstawieniu pod niego instancji
Log, to powinno działać? Jeśli umieszczę import w funkcji to
zaimportuje do słownika głównego modułu czy do lokalnego funkcji? Da
się tak w ogóle?
(nie jestem tutaj w stanie sprawdzić)
Ok, to co tak naprawdę robisz:
>>> m1 = {'log': None}
>>> m2 = {'Log': 'klasa Log'}
>>> m4 = {'log': m1['log']}
>>> m1['log'] = m2['Log']
>>> m1, m2, m4
({'log': 'klasa Log'}, {'Log': 'klasa Log'}, {'log': None})
Prosto, zwięźle i jasno. Nic dodać, nic ująć. Dziękuję. :)
przykład:
p1.m2
----------
import m1
import p2.m3
print m1.A.counter
a = m1.A()
print m1.A.counter
p2.m3.get()
p1.m1
---------
class A(object):
counter = 0
def __init__(self):
A.counter = 1
p2.m3
---------
import p1.m1
def get():
print "w def m3: " + str(p1.m1.A.counter)
Po uruchomieniu m2:
0
1
w def m3: 0
Długo kombinowałem jak sprawić aby w m3 widział obiekt A taki jakim
jest naprawdę. i w końcu wyszło, że trzeba przerobić m2:
p1.m2
---------
import p1.m1
import p2.m3
print p1.m1.A.counter
a = p1.m1.A()
print p1.m1.A.counter
p2.m3.get()
Teraz wynik jak oczekiwano:
0
1
w def m3: 1
Tylko, że przy pisaniu większego programu z setkami modułów
rozrzuconych po różnych paczkach to staje się horrorem. Już teraz ok.
80% czasu zajmuje mi kombinowanie z importowaniem aby wszędzie było
widziane to co ma być. Już nie mówię o zmorze cyklicznych odwołań
pomiędzy modułami.
Jest na to jakiś sposób, żeby to opanować?
Jakieś rady?
Tak, próbowałem i ciągle próbuję to zrozumieć ale co chwila się łapie
na tym, że już mi się wydawało, że wszystko ok i znowu coś nie działa.
Coś jest totalnie nie tak z architekturą twoich modułów.
Ja bym zrobił na przykład tak:
p2.m3
---------
class A:
"""abstrakcyjna klasa do nadpisania przez inny moduł"
__init__():
raise NotImplementedError
def get():
counter = A()
print "w def m3: " + str(counter)
p1.m2
----------
import m1
import p2.m3
print m1.A.counter
a = m1.A()
print m1.A.counter
p2.m3.A = m1.A
p2.m3.get()