__getattr__ bir modülde


127

A'nın eşdeğerini __getattr__bir sınıfa, bir modüle nasıl uygulayabilirim ?

Misal

Bir modülün statik olarak tanımlanmış özniteliklerinde bulunmayan bir işlevi çağırırken, o modülde bir sınıfın bir örneğini oluşturmak ve modüldeki öznitelik aramasında başarısız olanla aynı adla onun üzerindeki yöntemi çağırmak istiyorum.

class A(object):
    def salutation(self, accusative):
        print "hello", accusative

# note this function is intentionally on the module, and not the class above
def __getattr__(mod, name):
    return getattr(A(), name)

if __name__ == "__main__":
    # i hope here to have my __getattr__ function above invoked, since
    # salutation does not exist in the current namespace
    salutation("world")

Hangi verir:

matt@stanley:~/Desktop$ python getattrmod.py 
Traceback (most recent call last):
  File "getattrmod.py", line 9, in <module>
    salutation("world")
NameError: name 'salutation' is not defined

2
Her koşulda işe yaradığı için muhtemelen yasın cevabını kullanacağım (biraz dağınık ve daha iyi yapılabilir olsa da). Harvard S ve S Lott'un güzel ve temiz cevapları var ama pratik çözümler değiller.
Matt Joiner

1
Sizin durumunuzda bir öznitelik erişimi bile yapmıyorsunuz, bu yüzden aynı anda iki farklı şey istiyorsunuz. Yani asıl soru hangisini istediğinizdir. İstediğiniz musunuz salutationglobal veya yerel ad alanında var olmaya veya bir modül üzerinde bir nokta erişimini yaptığınızda isimlerin dinamik arama istiyorsun (kod yukarıda yapmaya çalıştığı şey budur)? Bu iki farklı şey.
Lennart Regebro

İlginç soru, bunu nasıl buldunuz?
Chris Wesseling


1
__getattr__modüller üzerinde Python 3.7
Fumito Hamamura

Yanıtlar:


42

Bir süre önce Guido, yeni stil sınıflarındaki tüm özel yöntem aramalarının __getattr__ve__getattribute__ . Dunder yöntemleri daha önce modüller üzerinde çalışmıştı - örneğin, bir modülü basitçe tanımlayarak __enter__ve __exit__bu hileler bozulmadan önce bir bağlam yöneticisi olarak kullanabilirsiniz .

Son zamanlarda, bazı tarihsel özellikler bir geri dönüş yaptı, __getattr__bunların arasındaki modül ve bu nedenle mevcut hack ( sys.modulesiçe aktarma zamanında kendisini bir sınıfla değiştiren bir modül ) artık gerekli olmamalı.

Python 3.7+ sürümünde, yalnızca tek bir bariz yolu kullanırsınız. Bir modülde öznitelik erişimini özelleştirmek __getattr__için, modül düzeyinde bir bağımsız değişkeni (öznitelik adı) kabul etmesi gereken bir işlev tanımlayın ve hesaplanan değeri döndürür veya bir AttributeError:

# my_module.py

def __getattr__(name: str) -> Any:
    ...

Bu aynı zamanda "nereden" içe aktarımlara kancalara izin verir, yani gibi ifadeler için dinamik olarak oluşturulmuş nesneleri döndürebilirsiniz from my_module import whatever.

İlgili bir notta, getattr modülüyle birlikte __dir__yanıt vermek için modül düzeyinde bir işlev de tanımlayabilirsiniz dir(my_module). Ayrıntılar için PEP 562'ye bakın.


1
m = ModuleType("mod")Ve set ile dinamik olarak bir modül oluşturursam m.__getattr__ = lambda attr: return "foo"; Ancak, ben çalıştırdığınızda from mod import foo, ben olsun TypeError: 'module' object is not iterable.
weberc2

@ weberc2: Bunu yapın m.__getattr__ = lambda attr: "foo", ayrıca modül için bir giriş tanımlamanız gerekir sys.modules['mod'] = m. Daha sonra, ile ilgili bir hata yok from mod import foo.
martineau

wim: Modül düzeyinde bir özelliğe sahip olmak gibi dinamik olarak hesaplanan değerler de elde edebilirsiniz (bir modülden my_module.whateversonra import my_module) yazmak için izin verin .
martineau

115

Burada karşılaştığınız iki temel sorun var:

  1. __xxx__ yöntemler yalnızca sınıfta aranır
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) herhangi bir çözümün hangi modülün incelendiğini de takip etmesi gerektiği anlamına gelir, aksi takdirde her modül o zaman örnek değiştirme davranışına sahip olur; ve (2) (1) mümkün bile değil ... en azından doğrudan değil.

Neyse ki, sys.modules oraya neyin gireceği konusunda seçici değildir, bu nedenle bir sarmalayıcı çalışacaktır, ancak yalnızca modül erişimi için (yani import somemodule; somemodule.salutation('world'); aynı modül erişimi için, yöntemleri ikame sınıfından globals()çekmeniz ve bunları eiher'e eklemeniz gerekir. Sınıfta özel yöntem (kullanmayı seviyorum .export()) veya genel bir işlevle (zaten yanıtlar olarak listelenenler gibi) Akılda tutulması gereken bir şey: sarmalayıcı her seferinde yeni bir örnek oluşturuyorsa ve global çözüm değilse, ince bir şekilde farklı davranışlarla sonuçlanırsınız .. Oh, ve ikisini aynı anda kullanamazsınız - biri ya da diğeri.


Güncelleme

Gönderen Guido van Rossum :

Aslında ara sıra kullanılan ve önerilen bir hack vardır: bir modül, istenen işlevselliğe sahip bir sınıfı tanımlayabilir ve ardından sonunda, kendisini sys.modules içinde bu sınıfın bir örneğiyle (veya ısrar ederseniz sınıfla değiştirebilir) , ancak bu genellikle daha az faydalıdır). Örneğin:

# module foo.py

import sys

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>

sys.modules[__name__] = Foo()

Bu işe yarıyor çünkü içe aktarma makinesi bu hack'i aktif olarak etkinleştiriyor ve son adımı gerçek modülü yükledikten sonra sys.modules'ten çekiyor. (Bu bir tesadüf değil. Hack uzun zaman önce önerilmişti ve onu ithalat makinelerinde destekleyecek kadar sevdiğimize karar verdik.)

Dolayısıyla, istediğiniz şeyi başarmanın yerleşik yolu, modülünüzde tek bir sınıf oluşturmaktır ve modülün son eylemi olarak sys.modules[__name__]sınıfınızın bir örneğini değiştirir - ve artık gerektiğinde __getattr__/ __setattr__/ ile oynayabilirsiniz __getattribute__.


Not 1 : Bu işlevi kullanırsanız modüldeki globals, diğer işlevler vb. Gibi diğer sys.modulesher şey , atama yapıldığında kaybolacaktır - bu nedenle, gereken her şeyin yedek sınıfın içinde olduğundan emin olun.

Not 2 : Desteklemek için sınıfta tanımlamış from module import *olmanız gerekir __all__; Örneğin:

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>
    __all__ = list(set(vars().keys()) - {'__module__', '__qualname__'})

Python sürümünüze bağlı olarak, atlanacak başka isimler de olabilir __all__. set()Python 2 uyumluluğu gerekli değildir göz ardı edilebilir.


3
Bu işe yarıyor çünkü içe aktarma makinesi bu hacklemeyi aktif olarak etkinleştiriyor ve son adımı gerçek modülü yükledikten sonra sys.modules'ten çekiyor . Dokümanlarda bir yerde bahsediliyor mu?
Piotr Dobrogost

4
Şimdi bunu göz önünde bulundurarak, bu kesmek kullanarak daha rahat hissetmeye "yarı yaptırıma" :)
Mark Nunberg

3
Bu yapım gibi, garip şeyler yapıyor import sysvermek Noneiçin sys. Bu hacklemenin Python 2'de onaylanmadığını tahmin ediyorum.
asmeurer

3
@asmeurer: Bunun nedenini (ve bir çözümü) anlamak için şu soruya bakın __name__ değeri sys.modules [__ name__] 'e atandıktan sonra neden değişiyor? .
martineau

1
@qarma: Python modüllerinin sınıf kalıtım modeline daha doğrudan katılmasına izin verecek bazı geliştirmelerin konuşulduğunu hatırlıyorum, ancak yine de bu yöntem hala çalışıyor ve destekleniyor.
Ethan Furman

48

Bu bir hack'tir, ancak modülü bir sınıfla sarmalayabilirsiniz:

class Wrapper(object):
  def __init__(self, wrapped):
    self.wrapped = wrapped
  def __getattr__(self, name):
    # Perform custom logic here
    try:
      return getattr(self.wrapped, name)
    except AttributeError:
      return 'default' # Some sensible default

sys.modules[__name__] = Wrapper(sys.modules[__name__])

10
güzel ve kirli: D
Matt Joiner

Bu işe yarayabilir, ancak muhtemelen yazarın gerçek sorununa bir çözüm değildir.
DasIch

12
"Çalışabilir" ve "muhtemelen hayır" pek yardımcı olmuyor. Bu bir hack / hile, ancak işe yarıyor ve sorunun ortaya çıkardığı sorunu çözüyor.
Håvard S

6
Bu, modülünüzü içe aktaran ve üzerinde var olmayan özniteliklere erişen diğer modüllerde çalışacak olsa da, buradaki gerçek kod örneği için çalışmayacaktır. Globals () 'a erişim sys.modules'den geçmez.
Marius Gedminas

5
Maalesef bu, mevcut modül için veya bir import *.
Matt Joiner

19

Genelde bu şekilde yapmayız.

Yaptığımız şey bu.

class A(object):
....

# The implicit global instance
a= A()

def salutation( *arg, **kw ):
    a.salutation( *arg, **kw )

Neden? Böylece örtük küresel örnek görülebilir.

Örnekler için, random"basit" bir rastgele sayı üreteci istediğiniz kullanım durumlarını biraz basitleştirmek için örtük bir genel örnek oluşturan modüle bakın .


Eğer konum gerçekten iddialı, sınıfı oluşturmak ve tekrarlayın tüm yöntemlerle ve her yöntem için bir modül düzeyi işlevi oluşturabilir.
Paul Fisher

@Paul Fisher: Soruna göre sınıf zaten var. Sınıfın tüm yöntemlerini ortaya çıkarmak iyi bir fikir olmayabilir. Genellikle bu maruz bırakılan yöntemler "uygunluk" yöntemleridir. Örtük küresel örnek için tümü uygun değildir.
S.Lott

13

@ Håvard S'nin önerdiği gibi, bir modüle biraz sihir uygulamam gereken bir durumda (gibi __getattr__), onu miras alan types.ModuleTypeve içine yerleştiren yeni bir sınıf tanımlardım sys.modules(muhtemelen özelliğimin ModuleTypetanımlandığı modülü değiştirerek ).

Bunun oldukça sağlam bir uygulaması için Werkzeug'un ana __init__.pydosyasına bakın .


8

Bu hilekârlık ama ...

import types

class A(object):
    def salutation(self, accusative):
        print "hello", accusative

    def farewell(self, greeting, accusative):
         print greeting, accusative

def AddGlobalAttribute(classname, methodname):
    print "Adding " + classname + "." + methodname + "()"
    def genericFunction(*args):
        return globals()[classname]().__getattribute__(methodname)(*args)
    globals()[methodname] = genericFunction

# set up the global namespace

x = 0   # X and Y are here to add them implicitly to globals, so
y = 0   # globals does not change as we iterate over it.

toAdd = []

def isCallableMethod(classname, methodname):
    someclass = globals()[classname]()
    something = someclass.__getattribute__(methodname)
    return callable(something)


for x in globals():
    print "Looking at", x
    if isinstance(globals()[x], (types.ClassType, type)):
        print "Found Class:", x
        for y in dir(globals()[x]):
            if y.find("__") == -1: # hack to ignore default methods
                if isCallableMethod(x,y):
                    if y not in globals(): # don't override existing global names
                        toAdd.append((x,y))


for x in toAdd:
    AddGlobalAttribute(*x)


if __name__ == "__main__":
    salutation("world")
    farewell("goodbye", "world")

Bu, genel ad alanındaki tüm nesneler üzerinde yineleme yaparak çalışır. Öğe bir sınıfsa, sınıf nitelikleri üzerinde yinelenir. Eğer öznitelik çağrılabilirse, onu bir işlev olarak global ad alanına ekler.

"__" içeren tüm öznitelikleri yok sayar.

Bunu üretim kodunda kullanmazdım, ancak başlamanız gerekir.


2
Håvard S'nin cevabını, çok daha temiz göründüğü için tercih ederim, ancak bu, sorulduğu gibi soruyu doğrudan yanıtlıyor.
üzülmek

Bu, nihayetinde gittiğim şeye çok daha yakın. Biraz dağınık, ancak aynı modül içinde globals () ile doğru şekilde çalışıyor.
Matt Joiner

1
Bana öyle geliyor ki bu cevap tam olarak sorulan şey değil, "Bir modülün statik olarak tanımlanmış özniteliklerinde bulunmayan bir işlevi çağırırken" çünkü koşulsuz olarak çalışıyor ve olası her sınıf yöntemini ekliyor. Bu, yalnızca AddGlobalAttribute()bir modül seviyesi varken AttributeError- @ Håvard S'nin mantığının tersi bir şekilde - yapan bir modül sarıcı kullanılarak düzeltilebilir . Eğer bir şansım olursa, bunu test edeceğim ve OP bu cevabı zaten kabul etmiş olsa bile kendi hibrit cevabımı ekleyeceğim.
martineau

Önceki yorumuma güncelle. NameErrorKüresel (modül) ad alanı için istisnaları yakalamanın çok zor (impssoble?) Olduğunu şimdi anlıyorum - bu, bu yanıtın bulduğu her olasılık için mevcut global ad alanına neden çağrılabilirler eklediğini ve her olası durumu önceden kapsadığını açıklıyor.
martineau

5

İşte benim mütevazı katkım - @ Håvard S'nin yüksek puan alan cevabının hafif bir süslemesi, ancak biraz daha açık (bu nedenle, muhtemelen OP için yeterince iyi olmasa da @ S.Lott için kabul edilebilir olabilir):

import sys

class A(object):
    def salutation(self, accusative):
        print "hello", accusative

class Wrapper(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped

    def __getattr__(self, name):
        try:
            return getattr(self.wrapped, name)
        except AttributeError:
            return getattr(A(), name)

_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])

if __name__ == "__main__":
    _globals.salutation("world")

-3

Sınıflarınızı içeren modül dosyanızı oluşturun. Modülü içe aktarın. getattrYeni içe aktardığınız modül üzerinde çalıştırın . __import__Modülü kullanarak ve sys.modules'ten dinamik içe aktarma yapabilirsiniz.

İşte modülünüz some_module.py:

class Foo(object):
    pass

class Bar(object):
    pass

Ve başka bir modülde:

import some_module

Foo = getattr(some_module, 'Foo')

Bunu dinamik olarak yapmak:

import sys

__import__('some_module')
mod = sys.modules['some_module']
Foo = getattr(mod, 'Foo')

1
Burada farklı bir soruyu yanıtlıyorsunuz.
Marius Gedminas
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.