एक मॉड्यूल पर __getattr__


127

__getattr__एक मॉड्यूल पर एक वर्ग के बराबर, कैसे लागू कर सकते हैं ?

उदाहरण

किसी फ़ंक्शन को कॉल करते समय, जो मॉड्यूल की वैधानिक रूप से परिभाषित विशेषताओं में मौजूद नहीं है, मैं उस मॉड्यूल में एक वर्ग का एक उदाहरण बनाना चाहता हूं, और उस पर विधि को उसी नाम से आह्वान करता हूं, जैसा कि मॉड्यूल पर विशेषता देखने में विफल रहा है।

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")

जो देता है:

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
मैं संभवतः शोक के उत्तर के साथ जाऊंगा, क्योंकि यह सभी परिस्थितियों में काम करता है (यद्यपि यह थोड़ा गड़बड़ है और इसे बेहतर किया जा सकता है)। हार्वर्ड एस और एस लोट के पास अच्छे स्वच्छ उत्तर हैं लेकिन वे व्यावहारिक समाधान नहीं हैं।
मैट जॉइनर

1
आप अपने मामले में भी एक विशेषता का उपयोग नहीं कर रहे हैं, इसलिए आप एक ही बार में दो अलग-अलग चीजों के लिए पूछ रहे हैं। तो प्रमुख प्रश्न यह है कि आप कौन सा चाहते हैं। क्या आप salutationवैश्विक या स्थानीय नामस्थान में मौजूद हैं (जो ऊपर वाला कोड करने की कोशिश कर रहा है) या क्या आप किसी मॉड्यूल पर डॉट एक्सेस करते समय नामों का डायनेमिक लुकअप चाहते हैं? यह दो अलग चीजें हैं।
लेनार्ट रेगेब्र

दिलचस्प सवाल, आप इसे कैसे लेकर आए?
क्रिस वेसलिंग


1
__getattr__मॉड्यूल पर Python 3.7
Fumito Hamamura

जवाबों:


42

कुछ समय पहले, गुइडो ने घोषणा की कि सभी विशेष पद्धति नए शैली के वर्गों पर बाईपास __getattr__और__getattribute__ । डंडर विधियों ने पहले मॉड्यूल पर काम किया था - उदाहरण के लिए, आप उन चालों को तोड़ने से पहले, केवल परिभाषित करके __enter__और संदर्भ के रूप में एक मॉड्यूल के रूप में मॉड्यूल का उपयोग कर सकते हैं ।__exit__

हाल ही में कुछ ऐतिहासिक विशेषताओं ने वापसी की है, __getattr__उनमें से मॉड्यूल और इसलिए मौजूदा हैक ( sys.modulesआयात समय में एक वर्ग के साथ खुद को बदलने वाला मॉड्यूल ) अब आवश्यक नहीं होना चाहिए।

Python 3.7+ में, आप बस एक स्पष्ट तरीके का उपयोग करते हैं। मॉड्यूल पर विशेषता पहुंच को अनुकूलित करने के लिए, __getattr__मॉड्यूल स्तर पर एक फ़ंक्शन को परिभाषित करें जिसे एक तर्क (विशेषता का नाम) को स्वीकार करना चाहिए, और गणना किए गए मान को वापस करना चाहिए या एक उठाना चाहिए AttributeError:

# my_module.py

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

यह "आयात" से "हुक" की अनुमति देगा, अर्थात आप इस तरह के बयानों के लिए गतिशील रूप से उत्पन्न वस्तुओं को वापस कर सकते हैं from my_module import whatever

संबंधित नोट पर, मॉड्यूल गेटैट के साथ-साथ आप __dir__प्रतिक्रिया करने के लिए मॉड्यूल स्तर पर एक फ़ंक्शन को भी परिभाषित कर सकते हैं dir(my_module)। देखें पीईपी 562 जानकारी के लिए।


1
यदि मैं गतिशील रूप से एक मॉड्यूल बनाता हूं m = ModuleType("mod")और सेट करता m.__getattr__ = lambda attr: return "foo"हूं; हालाँकि, जब मैं दौड़ता हूँ from mod import foo, मुझे मिलता है TypeError: 'module' object is not iterable
वेबर 2

@ weberc2: इसके m.__getattr__ = lambda attr: "foo"साथ ही, आपको मॉड्यूल के लिए एक प्रविष्टि को परिभाषित करने की आवश्यकता है sys.modules['mod'] = m। बाद में, कोई त्रुटि नहीं है from mod import foo
मार्टिन

wim: आप गतिशील रूप से गणना किए गए मान भी प्राप्त कर सकते हैं - जैसे कि मॉड्यूल-स्तरीय संपत्ति होना - किसी को my_module.whateverइसे लिखने के लिए लिखने की अनुमति देना (बाद में import my_module)।
मार्टीन्यू

115

यहां आप दो बुनियादी समस्याएं चला रहे हैं:

  1. __xxx__ तरीकों को केवल कक्षा पर देखा जाता है
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) का मतलब है कि किसी भी समाधान के लिए इस बात का भी ध्यान रखना होगा कि किस मॉड्यूल की जांच की जा रही है, अन्यथा प्रत्येक मॉड्यूल के पास उदाहरण-प्रतिस्थापन व्यवहार होगा; और (2) का मतलब है कि (1) संभव भी नहीं है ... कम से कम सीधे नहीं।

सौभाग्य से, sys.modules के बारे में अचार नहीं है कि वहां क्या चल रहा है इसलिए एक आवरण काम करेगा, लेकिन केवल मॉड्यूल एक्सेस के लिए (यानी import somemodule; somemodule.salutation('world'), समान-मॉड्यूल एक्सेस के लिए आपको प्रतिस्थापन वर्ग से विधियों को globals()भटकना होगा और उन्हें ei के साथ जोड़ना होगा ; वर्ग पर कस्टम विधि (मुझे पसंद है .export()) या एक जेनेरिक फ़ंक्शन के साथ (जैसे कि पहले से ही उत्तर के रूप में सूचीबद्ध)। एक बात का ध्यान रखें: यदि रैपर हर बार एक नया उदाहरण बना रहा है, और ग्लोबल्स समाधान नहीं है। आप अलग-अलग व्यवहार के साथ अंत करते हैं। ओह, और आपको एक ही समय में दोनों का उपयोग करने की आवश्यकता नहीं है - यह एक या दूसरे है।


अपडेट करें

से गुइडो van Rossum :

वास्तव में एक हैक है जिसे कभी-कभी उपयोग किया जाता है और सिफारिश की जाती है: एक मॉड्यूल वांछित कार्यक्षमता के साथ एक वर्ग को परिभाषित कर सकता है, और फिर अंत में, उस वर्ग के उदाहरण के साथ खुद को sys.modules में बदल सकता है (या वर्ग के साथ, यदि आप जोर देते हैं , लेकिन यह आम तौर पर कम उपयोगी है)। उदाहरण के लिए:

# module foo.py

import sys

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

sys.modules[__name__] = Foo()

यह काम करता है क्योंकि आयात मशीनरी सक्रिय रूप से इस हैक को सक्षम कर रही है, और जैसा कि इसका अंतिम चरण वास्तविक मॉड्यूल को sys.modules से बाहर निकालता है, इसे लोड करने के बाद। (यह कोई दुर्घटना नहीं है। हैक बहुत पहले प्रस्तावित किया गया था और हमने तय किया कि हम इसे आयात मशीनरी में समर्थन के लिए पर्याप्त पसंद करते हैं।)

इसलिए जो आप चाहते हैं उसे पूरा करने के लिए स्थापित तरीका आपके मॉड्यूल में एक एकल वर्ग बनाना है, और मॉड्यूल के अंतिम कार्य के रूप sys.modules[__name__]में आपकी कक्षा के एक उदाहरण के साथ प्रतिस्थापित किया जाता है - और अब आप आवश्यकतानुसार __getattr__/ __setattr__/ के साथ खेल सकते हैं __getattribute__


नोट 1 : यदि आप इस कार्यक्षमता का उपयोग करते हैं, तो मॉड्यूल में कुछ भी, जैसे ग्लोबल्स, अन्य फ़ंक्शन आदि, sys.modulesअसाइनमेंट किए जाने पर खो जाएगा - इसलिए सुनिश्चित करें कि प्रतिस्थापन वर्ग के अंदर आवश्यक सभी चीजें हैं।

नोट 2 : समर्थन करने के लिए from module import *आपको __all__कक्षा में परिभाषित होना चाहिए ; उदाहरण के लिए:

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

आपके पायथन संस्करण के आधार पर, इससे हटने के अन्य नाम भी हो सकते हैं __all__set()यदि अजगर 2 अनुकूलता की जरूरत नहीं है छोड़ा जा सकता है।


3
यह काम करता है क्योंकि आयात मशीनरी सक्रिय रूप से इस हैक को सक्षम कर रही है, और जैसा कि इसका अंतिम चरण वास्तविक मॉड्यूल को sys.modules से बाहर खींचता है, लोड करने के बाद क्या यह डॉक्स में कहीं उल्लेख किया गया है?
पियोट्र डोब्रोगोस्ट

4
अब मैं इस हैक का उपयोग करते हुए आसानी से अधिक महसूस कर रहा हूं, इसे "अर्ध-स्वीकृत" :)
मानते हुए

3
यह पेचीदा काम कर रहा है, जैसे import sysदेना देना Noneहै sys। मुझे लगता है कि इस हैक को पायथन 2 में मंजूरी नहीं दी गई है
साहसी

3
@asmeurer: उस (और एक समाधान) के कारण को समझने के लिए प्रश्न देखें कि __name__ का मान sys.modules को असाइन करने के बाद क्यों बदल रहा है [__ name__]?
मार्टीन्यू

1
@qarma: मुझे लगता है कि कुछ संवर्द्धन की बात की जा रही है जो कि अजगर मॉड्यूल को सीधे वर्ग उत्तराधिकार मॉडल में अधिक भाग लेने की अनुमति देगा, लेकिन फिर भी यह तरीका अभी भी काम करता है और समर्थित है।
एथन फुरमान

48

यह एक हैक है, लेकिन आप मॉड्यूल को क्लास में लपेट सकते हैं:

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
अच्छा और गंदा: डी
मैट जॉइनर

यह काम कर सकता है लेकिन यह लेखक की वास्तविक समस्या का समाधान नहीं है।
दस

12
"काम हो सकता है" और "शायद नहीं" बहुत उपयोगी नहीं है। यह एक हैक / ट्रिक है, लेकिन यह काम करता है, और सवाल द्वारा हल की गई समस्या को हल करता है।
होवार्ड एस

6
हालांकि यह अन्य मॉड्यूल में काम करेगा जो आपके मॉड्यूल को आयात करता है और उस पर कोई भी अशिष्ट गुण नहीं पहुंचता है, यह यहां वास्तविक कोड उदाहरण के लिए काम नहीं करेगा। ग्लोबल्स () तक पहुँचने के लिए sys.modules से गुज़रना नहीं पड़ता है।
मारियस गेदमिनस

5
दुर्भाग्य से यह वर्तमान मॉड्यूल के लिए काम नहीं करता है, या ए के बाद एक्सेस किए गए सामान के लिए संभावना है import *
मैट जॉइनर

19

हम आम तौर पर ऐसा नहीं करते हैं।

हम क्या करते हैं।

class A(object):
....

# The implicit global instance
a= A()

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

क्यों? ताकि निहित वैश्विक उदाहरण दिखाई दे।

उदाहरण के लिए, randomमॉड्यूल को देखें, जो उपयोग के मामलों को थोड़ा सरल बनाने के लिए एक अंतर्निहित वैश्विक उदाहरण बनाता है जहां आप "सरल" यादृच्छिक संख्या जनरेटर चाहते हैं।


यदि आप वास्तव में महत्वाकांक्षी हैं, तो आप कक्षा बना सकते हैं, और इसके सभी तरीकों के माध्यम से पुनरावृत्ति कर सकते हैं और प्रत्येक विधि के लिए एक मॉड्यूल-स्तरीय फ़ंक्शन बना सकते हैं।
पॉल फिशर

@ पाॅल फिशर: समस्या के अनुसार, वर्ग पहले से मौजूद है। कक्षा के सभी तरीकों को उजागर करना एक अच्छा विचार नहीं हो सकता है। आमतौर पर ये उजागर तरीके "सुविधा" के तरीके हैं। सभी अंतर्निहित वैश्विक उदाहरण के लिए उपयुक्त नहीं हैं।
लोट

13

इसी तरह @ Håvard S ने प्रस्तावित किया था, ऐसे मामले में जहां मुझे एक मॉड्यूल पर कुछ जादू लागू करने की आवश्यकता थी (जैसे __getattr__), मैं एक नए वर्ग को परिभाषित करूंगा जो कि विरासत में मिला है types.ModuleTypeऔर इसमें डाल दिया है sys.modules(संभवतः उस मॉड्यूल को बदलना जहां मेरा कस्टम ModuleTypeपरिभाषित किया गया था)।

इस के एक काफी मजबूत कार्यान्वयन के लिए Werkzeug की मुख्य __init__.pyफ़ाइल देखें ।


8

यह हैकिश है, लेकिन ...

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")

यह वैश्विक नेमस्पेस में सभी वस्तुओं पर पुनरावृति करके काम करता है। यदि आइटम एक वर्ग है, तो यह वर्ग विशेषताओं पर पुनरावृत्ति करता है। यदि विशेषता कॉल करने योग्य है, तो यह फ़ंक्शन के रूप में इसे वैश्विक नामस्थान में जोड़ता है।

यह उन सभी विशेषताओं को अनदेखा करता है जिनमें "__" होता है।

मैं इसका उपयोग उत्पादन कोड में नहीं करूंगा, लेकिन इसे आपको शुरू कर देना चाहिए।


2
मैं Håvard S के जवाब को पसंद करता हूं, क्योंकि यह बहुत साफ दिखाई देता है, लेकिन यह सीधे पूछे गए सवाल का जवाब देता है।
शोक

यह एक बहुत करीब है जो मैं अंततः साथ गया था। यह थोड़ा गड़बड़ है, लेकिन ग्लोबल्स () के साथ एक ही मॉड्यूल में सही ढंग से काम करता है।
मैट जॉइनर

1
मेरे लिए ऐसा लगता है कि यह उत्तर वह नहीं है जिसके लिए पूछा गया था, "जब एक फ़ंक्शन को कॉल करना जो मॉड्यूल की सांख्यिकीय रूप से परिभाषित विशेषताओं में मौजूद नहीं है" क्योंकि यह ऐसा कर रहा है जो बिना शर्त काम कर रहा है और हर संभव वर्ग विधि जोड़ रहा है। एक मॉड्यूल आवरण का उपयोग करके तय किया जा सकता है कि केवल AddGlobalAttribute()जब कोई मॉड्यूल स्तर होता है AttributeError- @ Håvard S के तर्क के विपरीत। अगर मेरे पास एक मौका है तो मैं इसका परीक्षण करूंगा और अपना स्वयं का हाइब्रिड उत्तर जोड़ूंगा, हालांकि ओपी ने पहले ही इस जवाब को स्वीकार कर लिया है।
मार्टिउ

मेरी पिछली टिप्पणी के लिए अद्यतन करें। अब मुझे समझ में आया है कि यह NameErrorवैश्विक (मॉड्यूल) नेमस्पेस के अपवादों को रोकना बहुत कठिन (असंभव ) है - जो यह बताता है कि क्यों यह उत्तर-काल में आने वाले हर संभावित मामले को कवर करने के लिए वर्तमान वैश्विक नामस्थान के लिए कॉलबिलिटी जोड़ता है।
1

5

यहाँ मेरा अपना विनम्र योगदान है - Håvard S का अत्यधिक मूल्यांकन किया गया उत्तर, लेकिन थोड़ा और अधिक स्पष्ट (इसलिए यह @ S.Lott के लिए स्वीकार्य हो सकता है, भले ही ओपी के लिए पर्याप्त अच्छा न हो):

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

अपनी मॉड्यूल फ़ाइल बनाएं जिसमें आपकी कक्षाएं हों। मॉड्यूल आयात करें। getattrउस मॉड्यूल पर चलाएं जिसे आपने अभी आयात किया है। आप __import__sys.modules से मॉड्यूल का उपयोग करके एक गतिशील आयात कर सकते हैं ।

यहाँ आपका मॉड्यूल है some_module.py:

class Foo(object):
    pass

class Bar(object):
    pass

और एक अन्य मॉड्यूल में:

import some_module

Foo = getattr(some_module, 'Foo')

गतिशील रूप से ऐसा करना:

import sys

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

1
आप यहां एक अलग प्रश्न का उत्तर दे रहे हैं।
मारियस गेदमिनस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.