سؤال

كيف يمكن تنفيذ ما يعادل __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
هل كانت مفيدة؟

المحلول

منذ فترة، أعلن Guido أن جميع عمليات البحث عن الطريقة الخاصة على فئات النمط الجديد __getattr__ و __getattribute__. وبعد لقد عملت أساليب DUNDER سابقا على وحدات - يمكنك، على سبيل المثال، استخدم وحدة نمطية كمدير سياق ببساطة عن طريق تحديد __enter__ و __exit__, ، قبل تلك الحيل حطم.

مؤخرا بعض الميزات التاريخية جعلت عودة، الوحدة __getattr__ من بينها، وبالتالي فإن الاختراق الموجود (وحدة تحل محل نفسها مع فئة في sys.modules في وقت الاستيراد) يجب أن يكون ضروريا.

في بيثون 3.7+، يمكنك فقط استخدام الطريقة الواضحة واحدة. لتخصيص الوصول إلى السمة على وحدة نمطية، حدد __getattr__ وظيفة على مستوى الوحدة النمطية والتي يجب أن تقبل حجة واحدة (اسم السمة)، وإرجاع القيمة المحسوبة أو رفع AttributeError:

# my_module.py

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

سيحصل ذلك أيضا على السنانير في "من" الواردات "، أي يمكنك إعادة كائنات تم إنشاؤها ديناميكيا للحصول على بيانات مثل from my_module import whatever.

في مذكرة ذات صلة، مع وحدة GetAttr قد تحدد أيضا __dir__ وظيفة في مستوى الوحدة النمطية للرد dir(my_module). وبعد يرى بيب 562. للتفاصيل.

نصائح أخرى

هناك نوعان مشكلتان أساسيتان تقوم به هنا:

  1. __xxx__ يتم البحث عن الطرق فقط في الفصل
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) يعني أن أي حل يجب أن يسيطر أيضا على تتبع الوحدة النمطية التي تم فحصها، وإلا كل ستكون الوحدة بعد ذلك سلوك استبدال المثيل؛ و (2) يعني أن (1) غير ممكن حتى ... على الأقل ليس مباشرة.

لحسن الحظ، لا يضلل syss.modules حول ما يذهب هناك حتى يعمل التفاف، ولكن فقط للوصول إلى الوحدة النمطية (أي import somemodule; somemodule.salutation('world'); ؛ لنفس الوحدة النمطية، يمكنك الوصول إليك إلى حد كبير من الأساليب من فئة الاستبدال وإضافتها إلى globals() EIHER مع طريقة مخصصة في الفصل (أحب استخدام .export()) أو مع وظيفة عامة (مثل تلك المدرجة بالفعل كجابات). شيء واحد يجب مراعاته: إذا كان التفاف إنشاء مثيل جديد في كل مرة، ومحلول Globals ليس كذلك، فأنت في نهاية المطاف بسلوك مختلف تماما. أوه، ولا تحصل على استخدام كلاهما في نفس الوقت - إنه واحد أو آخر.


تحديث

من عند غيدو فان روسوم:

هناك بالفعل اختراق يستخدم في بعض الأحيان ويوصى به: يمكن أن تحدد الوحدة النمطية فئة ذات الوظيفة المطلوبة، ثم في النهاية، استبدل نفسها في 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__ كما هو مطلوب.

لاحظ أنه إذا كنت تستخدم هذه الوظيفة أي شيء آخر في الوحدة النمطية، مثل Globals، وظائف أخرى، وما إلى ذلك، فسوف تضيع عندما sys.modules يتم الاحالة - لذلك تأكد من أن كل شيء مطلوب هو داخل فئة البديل.

هذا اختراق، ولكن يمكنك التفاف الوحدة مع فئة:

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__])

نحن لا نفعل ذلك عادة بهذه الطريقة.

ما نفعله هو هذا.

class A(object):
....

# The implicit global instance
a= A()

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

لماذا ا؟ بحيث المثال العالمي الضمني مرئي.

للحصول على أمثلة، انظر إلى random الوحدة النمطية، التي تنشئ مثيل عالمي ضمني لتبسيط حالات الاستخدام قليلا حيث تريد مولد رقم عشوائي "بسيط".

على غرار ما __getattr__)، أود أن أعرف فئة جديدة التي ترث من types.ModuleType ووضع ذلك في sys.modules (ربما استبدال الوحدة حيث عرف بلدي ModuleType تم تعريفه).

انظر الرئيسية __init__.py ملف Werkzeug. لتنفيذ قوي إلى حد ما لهذا.

هذا هو المخربقة، ولكن ...

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

يعمل هذا عن طريق التكرار على جميع الكائنات في مساحة الاسم العالمية. إذا كان العنصر فئة، فإنه يتكرر أكثر من سمات الفصل. إذا كانت السمة قابلة للاستدعاء، فإنها تضيفها إلى مساحة الاسم العالمية كدالة.

يتجاهل كل السمات التي تحتوي على "__".

لن أستخدم هذا في رمز الإنتاج، ولكن يجب أن تبدأك.

إليك مساهمتي المتواضعة الخاصة - تشاء طفيف في إجابة @ HÅVARD S HÅVARD S HHEVARD S.

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

قم بإنشاء ملف الوحدة النمطية الخاص بك الذي يحتوي على فصولك. استيراد الوحدة النمطية. يركض 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')

في بعض الحالات globals() يمكن القاموس كافية، على سبيل المثال، يمكنك إنشاء إصدار فئة بالاسم من النطاق العالمي:

from somemodule import * # imports SomeClass

someclass_instance = globals()['SomeClass']()
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top