Modüller, nesnelerin sahip olduğu özelliklere sahip olabilir mi?


102

Python özellikleriyle bunu öyle yapabilirim ki

obj.y 

sadece bir değer döndürmek yerine bir işlevi çağırır.

Bunu modüllerle yapmanın bir yolu var mı? İstediğim yerde bir davam var

module.y 

burada depolanan değeri döndürmek yerine bir işlevi çağırmak için.


3
Daha modern bir çözüm için __getattr__bir modüle bakın .
wim

Yanıtlar:


55

Yalnızca yeni stil sınıfların örnekleri özelliklere sahip olabilir. Python'u saklayarak böyle bir örneğin bir modül olduğuna inandırabilirsiniz sys.modules[thename] = theinstance. Dolayısıyla, örneğin, m.py modül dosyanız şöyle olabilir:

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

2
Bunu başka denen oldu mu? _M nedense ... değer Hiçbiri beri 'NoneType' nesne, hiçbir nitelik 'c' vardır: Bir dosya x.py bu kodu koymak ve daha sonra AttributeError içinde xy sonuçlar çağırarak, diğerinden içe zaman
Stephan202

3
Aslında kod, yorumlayıcı üzerinde çalışır. Ama bir dosyaya koyduğumda (örneğin, bowwow.py) ve onu başka bir dosyadan (otherfile.py) içe aktardığımda, artık çalışmıyor ...
Stephan202

4
S: Örneğin sınıfını types.ModuleType@ Unknown'da gösterildiği gibi türetmenin başka türlü çok benzer yanıtında herhangi bir özel avantaj (lar) olur mu?
martineau

12
Yalnızca yeni stil sınıfların örnekleri özelliklere sahip olabilir. Nedeni bu değil: modüller , kendilerinin builtins.modulebir örneği olan type(yeni stil sınıfının tanımıdır) örnekleri oldukları için yeni stil sınıflarının örnekleridir . Sorun özellikleri sınıfının değil, örnek olması gerektiğidir: bunu yaparsanız f = Foo(), f.some_property = property(...)sen safça bir modül içine koyun, sanki aynı şekilde başarısız olacak. Çözüm, onu sınıfa koymaktır, ancak tüm modüllerin özelliğe sahip olmasını istemediğiniz için, alt sınıfa ayırırsınız (bkz. Bilinmeyen'in cevabı).
Thanatos

3
@Joe, ismin yeniden bağlanması üzerine globals()(anahtarları sağlam tutmak ancak değerleri sıfırlamak None) sys.modulesbir Python 2 sorunudur - Python 3.4 amaçlandığı gibi çalışır. Py2'de sınıf nesnesine erişmeniz gerekiyorsa, örneğin ifadenin _M._cls = _Mhemen arkasına ekleyin class(veya başka bir ad alanında eşdeğer bir şekilde saklayın ) ve onu self._clsgerektiren yöntemlerde olduğu gibi erişin ( type(self)tamam olabilir, ancak alt sınıflandırma yaparsanız değil _M) .
Alex Martelli

54

Bunu, bir modülün tüm özniteliklerini doğru bir şekilde miras almak ve isinstance () ile doğru bir şekilde tanımlanmak için yapardım

import types

class MyModule(types.ModuleType):
    @property
    def y(self):
        return 5


>>> a=MyModule("test")
>>> a
<module 'test' (built-in)>
>>> a.y
5

Ve sonra bunu sys.modules'e ekleyebilirsiniz:

sys.modules[__name__] = MyModule(__name__)  # remember to instantiate the class

Bu, yalnızca en basit durumlarda işe yarıyor gibi görünüyor. Olası sorunlar şunlardır: (1) bazı ithalat yardımcıları, __file__manuel olarak tanımlanması gereken gibi başka öznitelikler de bekleyebilir , (2) sınıfı içeren modülde yapılan içe
aktarmalar

1
Bu bir alt sınıfı türetmek için gerekli değildir types.ModuleType, herhangi bir (yeni tarzı) sınıfı yapacağız. Devralmayı umduğunuz tam olarak hangi özel modül nitelikleri?
martineau

Ya orijinal modül bir paketse ve orijinal modülün altındaki modüllere erişmek istersem?
kawing-chiu

2
@martineau Bir modül repr'ine sahip olacaksınız, __init__bir örnek olduğunda modül adını belirtebilirsiniz ve kullanırken doğru davranışı elde edersiniz isinstance.
wim

@wim: Alınan puanlar, açıkçası hiçbiri o kadar önemli IMO değil.
martineau

35

Gibi PEP 562 Python hayata geçirildi> = 3.7, şimdi bunu yapabiliriz

dosya: module.py

def __getattr__(name):
    if name == 'y':
        return 3
    raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

other = 4

kullanım:

>>> 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'

Eğer ihmal varsa unutmayın raise AttributeErrorde __getattr__fonksiyonu, onunla fonksiyon biter anlamına gelir return None, ardından module.nosuchbir değer elde edecektir None.


2
Buna dayanarak başka bir cevap ekledim: stackoverflow.com/a/58526852/2124834

3
Bu bir mülkün sadece yarısıdır. Pasör yok.
wim

Maalesef bu tür özniteliklerin (?) ( Getattr yalnızca normal bir üye bulunmaması durumunda çağrılır)
olejorgenb

9

Dayanarak John Lin'in cevap :

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

Kullanım (baştaki alt çizgiye dikkat edin) the_module.py:

@module_property
def _thing():
    return 'hello'

Sonra:

import the_module

print(the_module.thing)  # prints 'hello'

Baştaki alt çizgi, özelliği özelliğine sahip işlevi orijinal işlevden ayırmak için gereklidir. Dekoratörün yürütülmesi sırasında henüz atanmamış olduğundan, tanımlayıcıyı yeniden atamanın bir yolunu düşünemedim.

IDE'lerin özelliğin var olduğunu bilmeyeceklerini ve kırmızı dalgaları göstereceğini unutmayın.


Harika! Sınıf özelliği ile karşılaştırıldığında, alt çizgi olmadan daha geleneksel @property def x(self): return self._xolduğunu düşünüyorum def thing(). Ve cevabınızda bir "modül özellik belirleyici" dekoratörü yaratabilir misiniz?
John Lin

2
@JohnLin, def thing()önerinizi uygulamaya çalıştım . Sorun şu ki, __getattr__sadece eksik özellikler için çağrılıyor . Ancak @module_property def thing(): …çalıştırmalardan sonra the_module.thingtanımlanır, bu nedenle getattr asla çağrılmaz. Bir şekilde thingdekoratörde kayıt olmalı ve sonra onu modülün ad alanından silmeliyiz. NoneDekoratörden dönmeyi denedim ama sonra thingolarak tanımlanıyor None. Biri yapabilirdi @module_property def thing(): … del thingama bunu thing()bir işlev olarak kullanmaktan daha kötü buluyorum
Ben Mares

Tamam "modül özellik belirleyici" veya "modül __getattribute__" olmadığını görüyorum . Teşekkür ederim.
John Lin

5

Tipik bir kullanım durumu şudur: (çok büyük) mevcut bir modülü bazı (birkaç) dinamik özellik ile zenginleştirmek - tüm modül öğelerini bir sınıf düzenine dönüştürmeden. Ne yazık ki en basit modül sınıfı yaması sys.modules[__name__].__class__ = MyPropertyModuleile başarısız olur TypeError: __class__ assignment: only for heap types. Dolayısıyla modül oluşturma işleminin yeniden yapılması gerekiyor.

Bu yaklaşım, Python içe aktarma kancaları olmadan, modül kodunun üstünde bir önsöze sahip olarak yapar:

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

Kullanım:

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

Not: Benzer from propertymodule import dynvalbir şey elbette donmuş bir kopya üretecektir - buna karşılık gelendynval = someobject.dynval


1

Kısa bir cevap: kullanım proxy_tools

proxy_toolsPaket sağlamak için çalışır @module_propertyözelliğe.

İle kurulur

pip install proxy_tools

İçinde, Marein en Örneğin @ hafif bir modifikasyon kullanma the_module.pykoyduk

from proxy_tools import module_property

@module_property
def thing():
    print(". ", end='')  # Prints ". " on each invocation
    return 'hello'

Şimdi başka bir senaryodan yapabilirim

import the_module

print(the_module.thing)
# . hello

Beklenmeyen davranış

Bu çözüm uyarılardan yoksun değildir. Yani the_module.thingolan bir dize değil ! proxy_tools.ProxyBir dizgeyi taklit edecek şekilde özel yöntemleri geçersiz kılınmış bir nesnedir. İşte bu noktayı gösteren bazı temel testler:

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']

Dahili olarak, orijinal işlev şu amaçlarla saklanır the_module.thing._Proxy__local:

print(res._Proxy__local)
# <function thing at 0x7f729c3bf680>

Diğer düşünceler

Dürüst olmak gerekirse, modüllerin neden bu işlevselliğe sahip olmadığı konusunda şaşkınım. Bence sorunun özü the_module, types.ModuleTypesınıfın bir örneği . Bir "modül özelliği" ayarlamak , sınıfın kendisi yerine bu sınıfın bir örneğinde bir özellik ayarlamak anlamına types.ModuleTypegelir. Daha fazla ayrıntı için bu yanıta bakın .

types.ModuleTypeSonuçlar harika olmasa da özellikleri aşağıdaki gibi uygulayabiliriz . Yerleşik türleri doğrudan değiştiremeyiz, ancak onları lanetleyebiliriz :

# 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__}'))

Bu bize tüm modüllerde var olan bir özellik verir. Tüm modüllerde ayar davranışını bozduğumuz için bu biraz zahmetli:

import sys

print(sys.thing2)
# hi from sys

sys.thing2 = 5
# AttributeError: can't set attribute

1
Bu, @Alex Martelli'nin cevabında gösterildiği gibi modülü gerçek bir sınıfın bir örneği yapmaktan nasıl daha iyidir?
martineau

1
Bana mantıklı gelmeyen başka bir şey söyledin. Bir @module_propertydekoratör sahibi olmakla ilgili bu işi ele alın . Genel olarak konuşursak, yerleşik @propertydekoratör, bir sınıf oluşturulduktan sonra değil, bir sınıf tanımlandığında kullanılır, bu nedenle aynı şeyin bir modül özelliği için de geçerli olacağını varsayardım ve Alex'in cevabıyla birlikte - bu soruyu hatırlayın "Modüllerin özellikleri nesnelerin yapabildiği gibi olabilir mi?". Ancak olduğunu sonradan bunları eklemek mümkündür ve benim daha önceki modifiye ettik pasajı yapılabilir bir yol göstermek için.
martineau

1
Ben: Somut örneğinizdeki koda baktıktan sonra, şu anda ne elde ettiğinizi anladığımı düşünüyorum. Ayrıca, Alex'in cevabında olduğu gibi modülün bir sınıf örneğiyle değiştirilmesini gerektirmeyen modül özelliklerine benzer bir şey uygulamak için bir teknik bulduğumu da düşünüyorum, ancak bu noktada yapmanın bir yolu olup olmadığından emin değilim. bir dekoratör aracılığıyla - herhangi bir ilerleme kaydedersem size geri döneceğim.
martineau

1
Tamam, işte ana fikri içeren başka bir sorunun cevabının bağlantısı .
martineau

1
Eh, bir durumunda, en azından cached_module_property, aslında __getattr__()nitelik tanımlı alırsa artık adı verilecek yararlıdır. ( functools.cached_propertybaşarıya benzer ).
martineau
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.