Các mô-đun có thể có các thuộc tính giống như các đối tượng không?


98

Với các thuộc tính của python, tôi có thể làm cho nó như vậy

obj.y 

gọi một hàm thay vì chỉ trả về một giá trị.

Có cách nào để làm điều này với các mô-đun? Tôi có một trường hợp mà tôi muốn

module.y 

để gọi một hàm, thay vì chỉ trả về giá trị được lưu trữ ở đó.


2
Xem __getattr__trên một mô-đun để biết giải pháp hiện đại hơn.
wim

Câu trả lời:


56

Chỉ các trường hợp của lớp kiểu mới mới có thể có thuộc tính. Bạn có thể làm cho Python tin rằng một ví dụ như vậy là một mô-đun bằng cách lưu trữ nó vào sys.modules[thename] = theinstance. Vì vậy, ví dụ: tệp mô-đun m.py của bạn có thể là:

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
Có ai khác đã thử cái này không? Khi tôi đặt mã này vào một tệp x.py và nhập nó từ tệp khác, sau đó gọi xy dẫn đến kết quả là AttributeError: Đối tượng 'NoneType' không có thuộc tính 'c', vì _M bằng cách nào đó có giá trị Không ...
Stephan202

3
Thật vậy, mã hoạt động trên trình thông dịch. Nhưng khi tôi đặt nó vào một tệp (giả sử, bowwow.py) và tôi nhập nó từ một tệp khác (otherfile.py), thì nó không còn hoạt động nữa ...
Stephan202

4
H: Có bất kỳ lợi thế cụ thể nào để lấy ra lớp của cá thể từ types.ModuleTypenhư được hiển thị trong câu trả lời rất giống @ Unknown's không?
martineau

11
Chỉ các trường hợp của lớp kiểu mới mới có thể có thuộc tính. Đây không phải là lý do: các mô-đun là các thể hiện của các lớp kiểu mới, trong đó chúng là các thể hiện của builtins.modulenó, bản thân nó là một thể hiện của type(đó là định nghĩa của lớp kiểu mới). Vấn đề là tính phải nằm trên lớp, không phải là ví dụ: nếu bạn làm f = Foo(), f.some_property = property(...)thì sẽ thất bại theo cách tương tự như khi bạn ngây thơ đặt nó trong một mô-đun. Giải pháp là đặt nó trong lớp, nhưng vì bạn không muốn tất cả các mô-đun có thuộc tính, nên bạn phân lớp (xem câu trả lời của Unknown).
Thanatos

3
@Joe, việc thay đổi globals()(giữ nguyên các khóa nhưng đặt lại giá trị thành None) khi tên của sys.modulesnó được liên kết lại là một vấn đề của Python 2 - Python 3.4 hoạt động như dự định. Nếu bạn cần quyền truy cập vào đối tượng lớp trong Py2, hãy thêm ví dụ _M._cls = _Mngay sau classcâu lệnh (hoặc lưu trữ nó một cách tương đương trong một số không gian tên khác) và truy cập nó như self._clstrong các phương thức yêu cầu nó ( type(self)có thể được nhưng không nếu bạn cũng thực hiện bất kỳ lớp con nào của _M) .
Alex Martelli

54

Tôi sẽ làm điều này để kế thừa đúng tất cả các thuộc tính của một mô-đun và được xác định chính xác bởi isinstance ()

import types

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


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

Và sau đó bạn có thể chèn cái này vào sys.modules:

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

Điều này dường như chỉ hoạt động đối với những trường hợp đơn giản nhất. Các vấn đề có thể xảy ra là: (1) một số người trợ giúp nhập cũng có thể mong đợi các thuộc tính khác như các thuộc tính __file__này phải được xác định theo cách thủ công, (2) các lần nhập được thực hiện trong mô-đun chứa lớp sẽ không "hiển thị" trong thời gian chạy, v.v.
tutuDajuju

1
Đó không phải là cần thiết để lấy được một lớp con từ types.ModuleType, bất kỳ (kiểu mới) lớp sẽ làm. Chính xác những thuộc tính mô-đun đặc biệt nào mà bạn muốn kế thừa?
martineau,

Điều gì sẽ xảy ra nếu mô-đun gốc là một gói và tôi muốn truy cập các mô-đun bên dưới mô-đun ban đầu?
kawing-chiu,

2
@martineau Bạn sẽ có một đại diện mô-đun, bạn có thể chỉ định tên mô-đun khi __init__một phiên bản và bạn sẽ có được hành vi chính xác khi sử dụng isinstance.
wim

@wim: Số điểm đã được thực hiện, mặc dù thành thật mà nói dường như không có IMO quan trọng nào.
martineau

34

PEP 562 đã được triển khai bằng Python> = 3.7, bây giờ chúng ta có thể làm điều này

tệp: module.py

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

other = 4

sử dụng:

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

Lưu ý rằng nếu bạn bỏ qua hàm raise AttributeErrortrong __getattr__hàm, điều đó có nghĩa là hàm kết thúc bằng return None, sau đó module.nosuchsẽ nhận giá trị là None.


2
Dựa trên điều này, tôi đã thêm một câu trả lời khác: stackoverflow.com/a/58526852/2124834

3
Đây chỉ là một nửa của tài sản. Không có người định cư.
wim

Thật không may, dường như không khó để làm cho công cụ nhận thức được các thuộc tính như vậy (?) ( Getattr chỉ được gọi nếu không tìm thấy thành viên thông thường)
olejorgenb

9

Dựa trên câu trả lời của John Lin :

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

Cách sử dụng (lưu ý dấu gạch dưới ở đầu), trong the_module.py:

@module_property
def _thing():
    return 'hello'

Sau đó:

import the_module

print(the_module.thing)  # prints 'hello'

Dấu gạch dưới hàng đầu là cần thiết để phân biệt hàm thuộc tính ized với hàm gốc. Tôi không nghĩ ra cách nào để gán lại số nhận dạng, vì trong thời gian thực thi decorator, nó vẫn chưa được gán.

Lưu ý rằng IDE sẽ không biết rằng thuộc tính tồn tại và sẽ hiển thị wavies màu đỏ.


Tuyệt quá! So với thuộc tính lớp, @property def x(self): return self._xtôi nghĩ rằng def thing()không có dấu gạch dưới là thông thường hơn. Và bạn cũng có thể tạo trình trang trí "trình thiết lập thuộc tính mô-đun" trong câu trả lời của mình không?
John Lin

2
@JohnLin, tôi đã cố gắng thực hiện def thing()đề xuất của bạn . Vấn đề là __getattr__chỉ được gọi cho các thuộc tính bị thiếu . Nhưng sau khi @module_property def thing(): …chạy, the_module.thingđược định nghĩa, vì vậy getattr sẽ không bao giờ được gọi. Chúng ta cần phải đăng ký bằng cách nào đó thingtrong decorator và sau đó xóa nó khỏi không gian tên của mô-đun. Tôi đã thử quay lại Nonetừ trình trang trí, nhưng sau đó thingđược xác định là None. Người ta có thể làm @module_property def thing(): … del thingnhưng tôi thấy điều đó tệ hơn việc sử dụng thing()như một hàm
Ben Mares

OK Tôi thấy không có "trình thiết lập thuộc tính mô-đun", cũng không có "mô-đun __getattribute__". Cảm ơn bạn.
John Lin

5

Một trường hợp sử dụng điển hình là: làm phong phú thêm một (rất lớn) mô-đun hiện có bằng một số (vài) thuộc tính động - mà không biến tất cả nội dung mô-đun thành một bố cục lớp. Thật không may, một bản vá lớp mô-đun đơn giản nhất như sys.modules[__name__].__class__ = MyPropertyModulekhông thành công với TypeError: __class__ assignment: only for heap types. Vì vậy việc tạo mô-đun cần phải được tua lại.

Cách tiếp cận này thực hiện điều đó mà không cần đến các móc nhập Python, chỉ bằng cách có một số đoạn mở đầu trên đầu mã mô-đun:

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

Sử dụng:

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

Lưu ý: Tất nhiên, một cái gì đó giống như from propertymodule import dynvalsẽ tạo ra một bản sao cố định - tương ứng vớidynval = someobject.dynval


1

Một câu trả lời ngắn gọn: sử dụng proxy_tools

Các proxy_toolsgói nỗ lực để cung cấp @module_propertychức năng.

Nó cài đặt với

pip install proxy_tools

Sử dụng một chút sửa đổi ví dụ của @ Marein, the_module.pychúng tôi đưa

from proxy_tools import module_property

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

Bây giờ từ một tập lệnh khác, tôi có thể làm

import the_module

print(the_module.thing)
# . hello

Hành vi không mong muốn

Giải pháp này không phải là không có cảnh báo. Cụ thể, the_module.thingkhông phải là một chuỗi ! Nó là một proxy_tools.Proxyđối tượng có các phương thức đặc biệt đã được ghi đè để nó bắt chước một chuỗi. Dưới đây là một số bài kiểm tra cơ bản minh họa quan điểm:

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

Trong nội bộ, chức năng gốc được lưu trữ để the_module.thing._Proxy__local:

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

Suy nghĩ thêm

Thành thật mà nói, tôi bối rối về lý do tại sao các mô-đun không được tích hợp sẵn chức năng này. Tôi nghĩ mấu chốt của vấn đề là đó the_modulelà một phiên bản của types.ModuleTypelớp. Đặt một "thuộc tính mô-đun" tương đương với việc đặt một thuộc tính trên một thể hiện của lớp này, chứ không phải trên types.ModuleTypechính lớp đó. Để biết thêm chi tiết, hãy xem câu trả lời này .

Chúng tôi thực sự có thể triển khai các thuộc tính trên types.ModuleTypenhư sau, mặc dù kết quả không lớn. Chúng tôi không thể sửa đổi trực tiếp các loại tích hợp sẵn, nhưng chúng tôi có thể nguyền rủa chúng:

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

Điều này cung cấp cho chúng tôi một thuộc tính tồn tại trên tất cả các mô-đun. Nó hơi khó sử dụng, vì chúng tôi phá vỡ hành vi cài đặt trên tất cả các mô-đun:

import sys

print(sys.thing2)
# hi from sys

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

1
Làm thế nào điều này tốt hơn là chỉ làm cho mô-đun trở thành một thể hiện của một lớp thực như được hiển thị trong câu trả lời của @Alex Martelli?
martineau

1
Bạn đã nói điều gì đó khác không có ý nghĩa với tôi. Hãy kinh doanh này về việc có một @module_propertyngười trang trí. Nói chung, trình @propertytrang trí tích hợp được sử dụng khi một lớp được xác định, không phải sau khi một thể hiện của nó đã được tạo, vì vậy tôi sẽ giả sử điều này cũng đúng với thuộc tính mô-đun và nó là với câu trả lời của Alex - nhớ lại câu hỏi này. "Mô-đun có thể có các thuộc tính giống như cách mà các đối tượng có thể không?". Tuy nhiên nó có thể để thêm chúng sau đó và tôi đã sửa đổi trước đây của tôi đoạn để minh họa một cách mà có thể được thực hiện.
martineau

1
Ben: Sau khi xem qua đoạn mã trong ví dụ cụ thể của bạn, tôi nghĩ tôi hiểu bạn đang nhận được gì. Tôi cũng nghĩ rằng gần đây tôi đã tình cờ gặp một kỹ thuật để triển khai một cái gì đó tương tự như các thuộc tính của mô-đun mà không yêu cầu mô-đun được thay thế bằng một cá thể lớp như trong câu trả lời của Alex, mặc dù tại thời điểm này tôi không chắc liệu có cách làm không nó thông qua một người trang trí - sẽ liên hệ lại với bạn nếu tôi đạt được bất kỳ tiến bộ nào.
martineau

1
OK, đây là liên kết đến câu trả lời cho một câu hỏi khác chứa ý tưởng cốt lõi.
martineau

1
Chà, ít nhất là trong trường hợp a cached_module_property, thực tế __getattr__()sẽ không còn được gọi nếu thuộc tính được xác định là hữu ích. (tương tự như những gì functools.cached_propertyhoàn thành).
martineau
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.