Dzięki właściwościom Pythona mogę to zrobić
obj.y
wywołuje funkcję, a nie tylko zwraca wartość.
Czy można to zrobić za pomocą modułów? Mam przypadek, w którym chcę
module.y
aby wywołać funkcję, zamiast zwracać przechowywaną tam wartość.
Dzięki właściwościom Pythona mogę to zrobić
obj.y
wywołuje funkcję, a nie tylko zwraca wartość.
Czy można to zrobić za pomocą modułów? Mam przypadek, w którym chcę
module.y
aby wywołać funkcję, zamiast zwracać przechowywaną tam wartość.
Odpowiedzi:
Tylko wystąpienia klas w nowym stylu mogą mieć właściwości. Możesz przekonać Pythona, że taka instancja jest modułem, przechowując ją w sys.modules[thename] = theinstance
. Na przykład twój plik modułu m.py może wyglądać następująco:
import sys
class _M(object):
def __init__(self):
self.c = 0
def afunction(self):
self.c += 1
return self.c
y = property(afunction)
sys.modules[__name__] = _M()
types.ModuleType
jak pokazano w bardzo podobnej odpowiedzi @ Unknown?
builtins.module
, które same w sobie są instancjami type
(co jest definicją klasy w nowym stylu). Problem polega na tym, że właściwości muszą znajdować się w klasie, a nie w instancji: jeśli to zrobisz f = Foo()
, f.some_property = property(...)
zakończy się niepowodzeniem w taki sam sposób, jak gdybyś naiwnie umieścił ją w module. Rozwiązaniem jest umieszczenie go w klasie, ale ponieważ nie chcesz, aby wszystkie moduły miały tę właściwość, tworzysz podklasę (zobacz odpowiedź Nieznane).
globals()
(utrzymywanie kluczy w stanie nienaruszonym, ale resetowanie wartości None
) po ponownym powiązaniu nazwy w sys.modules
jest problemem Pythona 2 - Python 3.4 działa zgodnie z przeznaczeniem. Jeśli potrzebujesz dostępu do obiektu klasy w Py2, dodaj np. Bezpośrednio _M._cls = _M
za class
instrukcją (lub schowaj ją równoważnie w jakiejś innej przestrzeni nazw) i uzyskaj do niego dostęp tak jak self._cls
w metodach, które tego wymagają ( type(self)
może być OK, ale nie, jeśli wykonasz również jakąkolwiek podklasę _M
) .
Zrobiłbym to, aby poprawnie odziedziczyć wszystkie atrybuty modułu i zostać poprawnie zidentyfikowanym przez isinstance ()
import types
class MyModule(types.ModuleType):
@property
def y(self):
return 5
>>> a=MyModule("test")
>>> a
<module 'test' (built-in)>
>>> a.y
5
Następnie możesz wstawić to do sys.modules:
sys.modules[__name__] = MyModule(__name__) # remember to instantiate the class
__file__
które muszą być zdefiniowane ręcznie, (2) importy wykonane w module zawierającym klasę nie będą "widoczne" w czasie wykonywania itd ...
types.ModuleType
, każda klasa (w nowym stylu) wystarczy. Jakie dokładnie atrybuty modułu mają zostać odziedziczone?
__init__
wystąpi, i uzyskasz prawidłowe zachowanie podczas używania isinstance
.
Ponieważ PEP 562 został zaimplementowany w Pythonie> = 3.7, teraz możemy to zrobić
plik: module.py
def __getattr__(name):
if name == 'y':
return 3
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
other = 4
stosowanie:
>>> import module
>>> module.y
3
>>> module.other
4
>>> module.nosuch
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "module.py", line 4, in __getattr__
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
AttributeError: module 'module' has no attribute 'nosuch'
Zauważ, że jeśli pominiesz raise AttributeError
w __getattr__
funkcji, oznacza to, że funkcja kończy się na return None
, a następnie module.nosuch
otrzyma wartość None
.
Na podstawie odpowiedzi Johna Lina :
def module_property(func):
"""Decorator to turn module functions into properties.
Function names must be prefixed with an underscore."""
module = sys.modules[func.__module__]
def base_getattr(name):
raise AttributeError(
f"module '{module.__name__}' has no attribute '{name}'")
old_getattr = getattr(module, '__getattr__', base_getattr)
def new_getattr(name):
if f'_{name}' == func.__name__:
return func()
else:
return old_getattr(name)
module.__getattr__ = new_getattr
return func
Użycie (zwróć uwagę na wiodące podkreślenie), w the_module.py
:
@module_property
def _thing():
return 'hello'
Następnie:
import the_module
print(the_module.thing) # prints 'hello'
Główny znak podkreślenia jest niezbędny do odróżnienia funkcji przypisanej własności od funkcji oryginalnej. Nie mogłem wymyślić sposobu na ponowne przypisanie identyfikatora, ponieważ w czasie wykonywania dekoratora nie został on jeszcze przypisany.
Zauważ, że IDE nie będą wiedzieć, że właściwość istnieje i pokażą czerwone fale.
@property def x(self): return self._x
myślę, że def thing()
bez podkreślenia jest bardziej konwencjonalne. Czy możesz również utworzyć dekorator „ustawiający właściwości modułu” w swojej odpowiedzi?
def thing()
sugestię. Problem polega na tym, że __getattr__
jest wywoływany tylko w przypadku brakujących atrybutów . Ale po @module_property def thing(): …
uruchomieniach the_module.thing
jest zdefiniowane, więc getattr nigdy nie zostanie wywołane. Musimy jakoś zarejestrować się thing
w dekoratorze, a następnie usunąć go z przestrzeni nazw modułu. Próbowałem wrócić None
od dekoratora, ale potem thing
jest zdefiniowany jako None
. Można to zrobić, @module_property def thing(): … del thing
ale uważam, że jest to gorsze niż używanie thing()
jako funkcji
__getattribute__
”. Dziękuję Ci.
Typowym przypadkiem użycia jest: wzbogacenie (ogromnego) istniejącego modułu kilkoma (kilkoma) atrybutami dynamicznymi - bez przekształcania wszystkich elementów modułu w układ klasy. Niestety najprostsza łatka na klasę modułu, jak na przykład, sys.modules[__name__].__class__ = MyPropertyModule
nie działa TypeError: __class__ assignment: only for heap types
. Dlatego tworzenie modułów wymaga ponownego okablowania.
To podejście robi to bez haków importu Pythona, po prostu mając prolog na górze kodu modułu:
# propertymodule.py
""" Module property example """
if '__orgmod__' not in globals():
# constant prolog for having module properties / supports reload()
print "PropertyModule stub execution", __name__
import sys, types
class PropertyModule(types.ModuleType):
def __str__(self):
return "<PropertyModule %r from %r>" % (self.__name__, self.__file__)
modnew = PropertyModule(__name__, __doc__)
modnew.__modclass__ = PropertyModule
modnew.__file__ = __file__
modnew.__orgmod__ = sys.modules[__name__]
sys.modules[__name__] = modnew
exec sys._getframe().f_code in modnew.__dict__
else:
# normal module code (usually vast) ..
print "regular module execution"
a = 7
def get_dynval(module):
return "property function returns %s in module %r" % (a * 4, module.__name__)
__modclass__.dynval = property(get_dynval)
Stosowanie:
>>> import propertymodule
PropertyModule stub execution propertymodule
regular module execution
>>> propertymodule.dynval
"property function returns 28 in module 'propertymodule'"
>>> reload(propertymodule) # AFTER EDITS
regular module execution
<module 'propertymodule' from 'propertymodule.pyc'>
>>> propertymodule.dynval
"property function returns 36 in module 'propertymodule'"
Uwaga: coś podobnego from propertymodule import dynval
spowoduje oczywiście zamrożoną kopię - odpowiadającądynval = someobject.dynval
proxy_tools
proxy_tools
Pakiet stara się zapewnić @module_property
funkcjonalność.
Instaluje się z
pip install proxy_tools
Używając niewielkiej modyfikacji przykładu @ Marein, the_module.py
umieściliśmy
from proxy_tools import module_property
@module_property
def thing():
print(". ", end='') # Prints ". " on each invocation
return 'hello'
Teraz z innego skryptu mogę zrobić
import the_module
print(the_module.thing)
# . hello
To rozwiązanie nie jest pozbawione zastrzeżeń. Mianowicie niethe_module.thing
jest to ciąg ! Jest to proxy_tools.Proxy
obiekt, którego specjalne metody zostały nadpisane, tak że naśladuje łańcuch. Oto kilka podstawowych testów, które ilustrują ten punkt:
res = the_module.thing
# [No output!!! Evaluation doesn't occur yet.]
print(type(res))
# <class 'proxy_tools.Proxy'>
print(isinstance(res, str))
# False
print(res)
# . hello
print(res + " there")
# . hello there
print(isinstance(res + "", str))
# . True
print(res.split('e'))
# . ['h', 'llo']
Wewnętrznie oryginalna funkcja jest przechowywana w the_module.thing._Proxy__local
:
print(res._Proxy__local)
# <function thing at 0x7f729c3bf680>
Szczerze mówiąc, zastanawiam się, dlaczego moduły nie mają wbudowanej tej funkcji. Myślę, że sedno sprawy polega na tym, że the_module
jest to instancja types.ModuleType
klasy. Ustawienie „właściwości modułu” jest równoznaczne z ustawieniem właściwości instancji tej klasy, a nie types.ModuleType
samej klasy. Aby uzyskać więcej informacji, zobacz tę odpowiedź .
Możemy faktycznie zaimplementować właściwości w types.ModuleType
następujący sposób, chociaż wyniki nie są świetne. Nie możemy bezpośrednio modyfikować typów wbudowanych, ale możemy je przeklinać :
# python -m pip install forbiddenfruit
from forbiddenfruit import curse
from types import ModuleType
# curse has the same signature as setattr.
curse(ModuleType, "thing2", property(lambda module: f'hi from {module.__name__}'))
Daje nam to właściwość, która istnieje we wszystkich modułach. Jest to trochę nieporęczne, ponieważ przerywamy zachowanie ustawień we wszystkich modułach:
import sys
print(sys.thing2)
# hi from sys
sys.thing2 = 5
# AttributeError: can't set attribute
@module_property
dekoratora. Ogólnie rzecz biorąc, wbudowany @property
dekorator jest używany podczas definiowania klasy, a nie po utworzeniu jej instancji, więc zakładam, że to samo dotyczy właściwości modułu i tak jest z odpowiedzią Alexa - przypomnij sobie to pytanie „Czy moduły mogą mieć takie same właściwości, jak obiekty?”. Jednak możliwe jest ich późniejsze dodanie i zmodyfikowałem mój wcześniejszy fragment, aby zilustrować sposób, w jaki można to zrobić.
cached_module_property
, fakt, że __getattr__()
nie będzie już wywoływany, jeśli atrybut zostanie zdefiniowany, jest pomocny. (podobnie do tego, co functools.cached_property
osiąga).
__getattr__
moduł dla bardziej nowoczesnego rozwiązania.