Bir modül değişkeni başka bir modülden nasıl değiştirilir?


111

Diyelim ki adında bir paketim var barve içinde bar.py:

a = None

def foobar():
    print a

ve __init__.py:

from bar import a, foobar

Sonra bu betiği çalıştırıyorum:

import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()

İşte beklediğim şey:

None
1
1

İşte aldığım şey:

None
1
None

Yanlış anlamamı kimse açıklayabilir mi?

Yanıtlar:


109

Kullanıyorsun from bar import a. aiçe aktaran modülün genel kapsamında (veya import ifadesi hangi kapsamda olursa olsun) bir sembol haline gelir.

Yeni bir değer atadığınızda , gerçek değeri değil, ayalnızca hangi değerin apuanını değiştirmiş olursunuz . Almayı deneyin bar.pydoğrudan import bariçinde __init__.pyve ayarı tarafından orada deney yapmak bar.a = 1. Bu şekilde, aslında bu bağlamda bar.__dict__['a']'gerçek' değeri olan değiştirmiş olacaksınız a.

Üç katmanla biraz kıvrımlıdır, ancak aslında türetilen modüldeki bar.a = 1değeri değiştirir . Gerçek dosyada yaşadığı için görenin değerini değiştirmez . Bunu değiştirmek istersen ayarlayabilirsin .abar__init__.pyafoobarfoobarbar.pybar.bar.a

Bu, ifadenin from foo import barbiçimini kullanmanın tehlikelerinden biridir import: bariki sembole bölünür ; biri küresel olarak görünür, içlerinden fooorijinal değeri işaret ederek başlar ve importifadenin yürütüldüğü kapsamda görünen farklı bir sembol . Bir sembolün işaret ettiği yeri değiştirmek, işaret ettiği değeri de değiştirmez.

Bu tür şeyler, reloadetkileşimli tercümandan bir modül almaya çalışırken katildir .


1
Teşekkürler! Cevabınız muhtemelen suçluyu aramaktan birkaç saat kurtardı.
jnns

Görünüşe göre bar.bar.ayaklaşım bile çoğu kullanım durumunda pek yardımcı olmayacak. Derleme veya modül yüklemesini ve çalışma zamanı atamalarını karıştırmak muhtemelen zayıf bir fikirdir çünkü sonunda kendinizin (veya kodunuzu kullanan diğerlerinin) kafasını karıştıracaksınız. Özellikle python'un tartışmasız yaptığı gibi, bu konuda biraz tutarsız davranan dillerde.
matanster

26

Bu soru ile zorluk kaynaklarından biri adlı bir program var olmasıdır bar/bar.py: import barithalat ya bar/__init__.pyya bar/bar.pyda biraz hantal hangi izlemek için yapar yapılır yere bağlı olarak aise bar.a.

Şu şekilde çalışır:

Ne anlamanın anahtarı Gözlerinde farklı olduğunu fark etmektir __init__.py,

from bar import a

aslında şöyle bir şey yapar

a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)

ve yeni bir değişken tanımlar ( bar/__init__.py:aisterseniz). Böylece, from bar import ain __init__.pyadınız bar/__init__.py:aorijinal bar.py:anesneye ( None) bağlanır . Yapabileceğiniz Bu yüzden from bar import a as a2de __init__.py: bu durumda, bu durum sizin de sahip olduğu açıktır bar/bar.py:ave bir ayrı değişken adı bar/__init__.py:a2sizin durumunuzda (iki değişken isimleri sadece ikisi olarak gerçekleşmesi a, ama yine de canlı farklı ad alanlarında bunlar: Giriş __init__.py, onlar bar.ave a).

Şimdi, ne zaman yaparsan

import bar

print bar.a

değişkene erişiyorsunuz bar/__init__.py:a(çünkü import barsizin değişkeninizi içe aktarıyor bar/__init__.py). Bu, değiştirdiğiniz değişkendir (1'e). Değişkenin içeriğine dokunmuyorsunuz bar/bar.py:a. Yani daha sonra yaptığında

bar.foobar()

Aradığınızda bar/bar.py:foobar()değişken erişen hangi aden bar/bar.pyhala hangi Nonezaman ( foobar()tanımlanmaktadır böylece, bu, ilk ve son olarak değişken isimleri bağlayan aiçinde bar.pyolan bar.py:aherhangi bir başka değil, açok var olabilecek başka tanımlanan değişken modül-olarak atüm ithal modüllerde değişkenleri ). Dolayısıyla son Noneçıktı.

Sonuç: herhangi bir karışıklıkları önlemek en iyisidir import bartarafından, değil herhangi sahip bar/bar.py(beri modülü bar.__init__.pymarkaları dizinine bar/da içe ki, bir paket zaten import bar).


12

Başka bir deyişle: Bu yanlış anlamayı yapmanın çok kolay olduğu ortaya çıktı. Python dil referansında sinsice tanımlanmıştır: sembol yerine nesne kullanımı . Python dili referansının bunu daha açık ve daha az seyrek hale getirmesini öneririm ..

fromBu tanımlayıcıları listesinden geçer adım (1) bulunan modülünde bunların her birini arar ve yerel ad alanında adını bağlar: formu değil bağlama modül adı yapar nesne böylece bulundu.

ANCAK:

İçe aktardığınızda, içe aktarılan sembolün geçerli değerini içe aktarır ve tanımlandığı şekilde ad alanınıza eklersiniz. Bir referansı içe aktarmıyorsunuz, bir değeri etkin bir şekilde içe aktarıyorsunuz.

Bu nedenle, güncellenmiş değerini almak için i, o sembole bir referans tutan bir değişkeni içe aktarmanız gerekir.

Başka bir deyişle, içe aktarma, importJAVA'daki bir, externalC / C ++ 'daki bildirime veya usePERL'deki bir tümce gibi DEĞİLDİR .

Bunun yerine, Python'da aşağıdaki ifade:

from some_other_module import a as x

daha çok K&R C'deki aşağıdaki kod gibidir :

extern int a; /* import from the EXTERN file */

int x = a;

(uyarı: Python durumunda, "a" ve "x" esasen gerçek değere bir referanstır: INT'yi kopyalamıyorsunuz, referans adresini kopyalıyorsunuz)


Aslında, Python'un yolunu importJava'dakinden çok daha temiz buluyorum , çünkü ad alanları / kapsamlar her zaman düzgün bir şekilde ayrı tutulmalı ve asla beklenmedik şekillerde birbirine müdahale etmemelidir. Burada olduğu gibi: Bir nesnenin bir isim alanındaki bir isme bağlanmasını değiştirmek (okuyun: bir modülün global özelliğine bir şey atamak) Python'daki diğer isim alanlarını (okuyun: içe aktarılan referans) asla etkilemez. Ancak bunu Java'da, vb. Yapar. Python'da yalnızca neyin içe aktarıldığını anlamanız gerekir, Java'da ise, bu içe aktarılan değeri daha sonra değiştirmesi durumunda diğer modülü de anlamanız gerekir.
Tino

2
Kesinlikle aynı fikirde olmamalıyım. İçe aktarma / dahil etme / kullanma, referans formunu kullanma konusunda uzun bir geçmişe sahiptir ve diğer tüm dillerdeki değer formunun değil.
Mark Gerolimatos

4
Kahretsin, dönüşe bastım… referansla ithalat beklentisi var (@OP uyarınca). Aslında, yeni bir Python programcısına, ne kadar deneyimli olursa olsun, bunu "dikkat" şeklinde anlatmak gerekir. Bu asla olmamalı: ortak kullanıma göre Python yanlış yolu seçti. Gerekirse bir "içe aktarma değeri" oluşturun, ancak içe aktarma sırasında sembolleri değerlerle karıştırmayın.
Mark Gerolimatos
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.