__getTtr__ على وحدة
-
20-09-2019 - |
سؤال
كيف يمكن تنفيذ ما يعادل __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. للتفاصيل.
نصائح أخرى
هناك نوعان مشكلتان أساسيتان تقوم به هنا:
__xxx__
يتم البحث عن الطرق فقط في الفصل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']()