Một số trường hợp sử dụng (cụ thể) cho kính thiên văn là gì?


118

Tôi có một người bạn thích sử dụng kính đeo, và thường xuyên đưa ra giải pháp cho chúng.

Tôi tâm niệm rằng bạn gần như không bao giờ cần sử dụng đến kính đeo. Tại sao? bởi vì tôi nghĩ nếu bạn đang làm điều gì đó tương tự với một lớp, thì có lẽ bạn nên làm điều đó với một đối tượng. Và một thiết kế lại / tái cấu trúc nhỏ đang được thực hiện.

Việc có thể sử dụng kính đeo tay đã khiến rất nhiều người ở nhiều nơi sử dụng các lớp học như một loại đối tượng hạng hai nào đó, điều này có vẻ rất tai hại đối với tôi. Lập trình có được thay thế bằng lập trình meta không? Thật không may, việc bổ sung các trình trang trí lớp đã khiến nó thậm chí còn được chấp nhận nhiều hơn.

Vì vậy, xin vui lòng, tôi rất muốn biết các trường hợp sử dụng hợp lệ (cụ thể) của bạn cho metaclasses trong Python. Hoặc để được hiểu tại sao các lớp đột biến lại tốt hơn các đối tượng đột biến, đôi khi.

Tôi sẽ bắt đầu:

Đôi khi khi sử dụng thư viện của bên thứ ba, rất hữu ích khi có thể thay đổi lớp theo một cách nhất định.

(Đây là trường hợp duy nhất tôi có thể nghĩ đến và nó không cụ thể)


3
Đâ là một câu hỏi tuyệt vời. Đánh giá từ các câu trả lời dưới đây, rõ ràng là không có cái gì gọi là sử dụng cụ thể cho kính đo.
Marcus Ottosson

Câu trả lời:


25

Tôi có một lớp xử lý âm mưu không tương tác, như một giao diện người dùng của Matplotlib. Tuy nhiên, đôi khi một người muốn thực hiện âm mưu tương tác. Chỉ với một vài hàm, tôi thấy rằng tôi có thể tăng số lượng hình vẽ, gọi vẽ thủ công, v.v., nhưng tôi cần thực hiện những việc này trước và sau mỗi lần gọi vẽ biểu đồ. Vì vậy, để tạo cả một trình bao bọc vẽ biểu đồ tương tác và một trình bao bọc biểu đồ ngoài màn hình, tôi thấy rằng thực hiện điều này thông qua metaclasses, gói các phương pháp thích hợp sẽ hiệu quả hơn so với thực hiện một số việc như:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

Phương thức này không theo kịp các thay đổi của API, v.v., nhưng một phương thức lặp lại các thuộc tính lớp __init__trước khi thiết lập lại các thuộc tính lớp sẽ hiệu quả hơn và luôn cập nhật mọi thứ:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

Tất nhiên, có thể có nhiều cách tốt hơn để làm điều này, nhưng tôi thấy cách này có hiệu quả. Tất nhiên, điều này cũng có thể được thực hiện trong __new__hoặc __init__, nhưng đây là giải pháp tôi thấy đơn giản nhất.


103

Gần đây tôi đã được hỏi cùng một câu hỏi và đã đưa ra một số câu trả lời. Tôi hy vọng có thể khôi phục chuỗi này, vì tôi muốn giải thích kỹ hơn về một số trường hợp sử dụng được đề cập và thêm một vài trường hợp mới.

Hầu hết các loại kính đeo mắt tôi từng thấy đều làm một trong hai điều:

  1. Đăng ký (thêm một lớp vào cấu trúc dữ liệu):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Bất cứ khi nào bạn phân lớp Model, lớp của bạn được đăng ký trong modelstừ điển:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}

    Điều này cũng có thể được thực hiện với trình trang trí lớp:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass

    Hoặc với chức năng đăng ký rõ ràng:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)

    Trên thực tế, điều này khá giống nhau: bạn đề cập đến trình trang trí lớp một cách không thuận lợi, nhưng nó thực sự không khác gì đường cú pháp cho một lệnh gọi hàm trên một lớp, vì vậy không có gì kỳ diệu về nó.

    Dù sao, lợi thế của metaclasses trong trường hợp này là tính kế thừa, vì chúng hoạt động với bất kỳ lớp con nào, trong khi các giải pháp khác chỉ hoạt động đối với các lớp con được trang trí hoặc đăng ký rõ ràng.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. Cấu trúc lại (sửa đổi các thuộc tính của lớp hoặc thêm các thuộc tính mới):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Bất cứ khi nào bạn phân lớp Modelvà xác định một số Fieldthuộc tính, chúng sẽ được đưa vào tên của chúng (ví dụ: đối với các thông báo lỗi nhiều thông tin hơn) và được nhóm vào một _fieldstừ điển (để dễ dàng lặp lại mà không cần phải xem qua tất cả các thuộc tính của lớp và tất cả các lớp cơ sở của nó ' thuộc tính mọi lúc):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}

    Một lần nữa, điều này có thể được thực hiện (không có kế thừa) với trình trang trí lớp:

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(

    Hoặc rõ ràng:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!

    Mặc dù, trái ngược với sự ủng hộ của bạn đối với lập trình không phải meta có thể đọc được và có thể bảo trì được, thì điều này lại cồng kềnh, dư thừa và dễ xảy ra lỗi hơn nhiều:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}

Đã xem xét các trường hợp sử dụng phổ biến và cụ thể nhất, các trường hợp duy nhất mà bạn hoàn toàn PHẢI sử dụng kính đo là khi bạn muốn sửa đổi tên lớp hoặc danh sách các lớp cơ sở, bởi vì sau khi được xác định, các tham số này được đưa vào lớp và không có trình trang trí hoặc chức năng có thể bỏ chúng.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

Điều này có thể hữu ích trong các khuôn khổ để đưa ra cảnh báo bất cứ khi nào các lớp có tên tương tự hoặc cây kế thừa không đầy đủ được xác định, nhưng tôi không thể nghĩ ra lý do nào ngoài việc trolling để thực sự thay đổi các giá trị này. Có lẽ David Beazley có thể.

Dù sao, trong Python 3, metaclasses cũng có __prepare__phương thức, cho phép bạn đánh giá phần thân của lớp thành một ánh xạ khác với a dict, do đó hỗ trợ các thuộc tính có thứ tự, các thuộc tính quá tải và những thứ thú vị khác:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

Bạn có thể lập luận rằng các thuộc tính có thứ tự có thể đạt được với bộ đếm tạo và quá trình nạp chồng có thể được mô phỏng bằng các đối số mặc định:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

Bên cạnh việc xấu hơn nhiều, nó cũng kém linh hoạt hơn: điều gì sẽ xảy ra nếu bạn muốn các thuộc tính theo nghĩa đen có thứ tự, như số nguyên và chuỗi? Nếu Nonelà giá trị hợp lệ xthì sao?

Đây là một cách sáng tạo để giải quyết vấn đề đầu tiên:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

Và đây là một cách sáng tạo để giải quyết vấn đề thứ hai:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

Nhưng điều này còn hơn rất nhiều so với một chiếc siêu thủy tinh đơn giản (đặc biệt là chiếc đầu tiên, thứ thực sự làm tan chảy bộ não của bạn). Quan điểm của tôi là, bạn nhìn kính thiên văn là xa lạ và phản trực quan, nhưng bạn cũng có thể coi chúng như bước tiến hóa tiếp theo của ngôn ngữ lập trình: bạn chỉ cần điều chỉnh tư duy của mình. Rốt cuộc, bạn có thể làm mọi thứ trong C, bao gồm cả việc xác định một cấu trúc với các con trỏ hàm và chuyển nó làm đối số đầu tiên cho các hàm của nó. Một người nhìn thấy C ++ lần đầu tiên có thể nói, "điều kỳ diệu này là gì? Tại sao trình biên dịch lại ngầm chuyểnthischo các phương thức, nhưng không cho các hàm thông thường và hàm tĩnh? Tốt hơn hết là hãy trình bày rõ ràng và dài dòng về các lập luận của bạn ". Nhưng sau đó, lập trình hướng đối tượng sẽ mạnh hơn nhiều khi bạn nắm được nó; và điều này cũng vậy, uh ... lập trình hướng đối tượng, tôi đoán vậy. Và một khi bạn hiểu được kính thiên văn, chúng thực sự rất đơn giản, vậy tại sao không sử dụng chúng khi thuận tiện?

Và cuối cùng, kính đo là rad, và lập trình sẽ rất thú vị. Sử dụng các cấu trúc lập trình tiêu chuẩn và các mẫu thiết kế mọi lúc đều nhàm chán và kém hấp dẫn, đồng thời cản trở trí tưởng tượng của bạn. Sống ngắn ngủi! Đây là một metametaclass, dành riêng cho bạn.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

Biên tập

Đây là một câu hỏi khá cũ, nhưng nó vẫn nhận được sự ủng hộ, vì vậy tôi nghĩ rằng tôi sẽ thêm một liên kết đến một câu trả lời toàn diện hơn. Nếu bạn muốn đọc thêm về kính đeo và công dụng của chúng, tôi vừa xuất bản một bài báo về nó ở đây .


5
Đó là một câu trả lời tuyệt vời, cảm ơn vì đã dành thời gian viết nó và đưa ra nhiều ví dụ
Chen A.

"... lợi thế của metaclasses trong trường hợp này là tính kế thừa, vì chúng hoạt động cho bất kỳ lớp con nào" - không phải trong Python 3, tôi cho là vậy? Tôi nghĩ rằng nó chỉ hoạt động trong Python 2 vì bất kỳ lớp con nào kế thừa__metaclass__ thuộc tính, nhưng thuộc tính này không còn đặc biệt trong Python 3. Có cách nào để làm cho "các lớp con cũng được xây dựng bởi siêu kính của cha mẹ" hoạt động trong Python 3 không ?
ForceBru

2
Điều này cũng đúng với Python 3, bởi vì một lớp B, kế thừa từ A, có siêu kính là M, cũng là một kiểu M. Vì vậy, khi B được đánh giá, M được gọi để tạo ra nó và điều này cho phép bạn để "làm việc trên bất kỳ lớp con nào" (của A). Phải nói rằng, Python 3.6 đã giới thiệu đơn giản hơn nhiều init_subclass, vì vậy bây giờ bạn có thể thao tác các lớp con trong một baseclass và không cần metaclass cho mục đích đó nữa.
Dan Gittik

Điều này thật tuyệt vời, tôi đã đọc rất nhiều bài đăng trên blog về metaclass, chỉ bài này mới biết được ưu nhược điểm và các lựa chọn thay thế cho metaclass.
ospider

36

Mục đích của metaclasses không phải để thay thế sự phân biệt lớp / đối tượng bằng metaclass / class - nó là để thay đổi hành vi của các định nghĩa lớp (và do đó là các thể hiện của chúng) theo một cách nào đó. Hiệu quả là nó thay đổi hành vi của câu lệnh lớp theo những cách có thể hữu ích hơn cho miền cụ thể của bạn so với mặc định. Những thứ tôi đã sử dụng chúng là:

  • Theo dõi các lớp con, thường là để đăng ký các trình xử lý. Điều này rất hữu ích khi sử dụng thiết lập kiểu plugin, nơi bạn muốn đăng ký một trình xử lý cho một thứ cụ thể chỉ đơn giản bằng cách phân lớp con và thiết lập một vài thuộc tính lớp. ví dụ. giả sử bạn viết một trình xử lý cho các định dạng nhạc khác nhau, trong đó mỗi lớp thực hiện các phương thức thích hợp (thẻ play / get, v.v.) cho loại của nó. Việc thêm một trình xử lý cho một kiểu mới sẽ trở thành:

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here

    Metaclass sau đó duy trì một từ điển {'.mp3' : MP3File, ... }, v.v. và xây dựng một đối tượng thuộc loại thích hợp khi bạn yêu cầu một trình xử lý thông qua một hàm gốc.

  • Thay đổi hành vi. Bạn có thể muốn gắn một ý nghĩa đặc biệt cho các thuộc tính nhất định, dẫn đến hành vi bị thay đổi khi chúng hiện diện. Ví dụ, bạn có thể muốn tìm kiếm các phương thức có tên _get_foo_set_foochuyển đổi chúng thành thuộc tính một cách minh bạch. Như một ví dụ trong thế giới thực, đây là một công thức tôi đã viết để cung cấp thêm các định nghĩa cấu trúc giống C. Metaclass được sử dụng để chuyển đổi các mục đã khai báo thành một chuỗi định dạng struct, xử lý kế thừa, v.v. và tạo ra một lớp có khả năng xử lý nó.

    Đối với các ví dụ khác trong thế giới thực, hãy xem các ORM khác nhau, như ORM của sqlalchemy hoặc sqlobject . Một lần nữa, mục đích là để giải thích các định nghĩa (ở đây là định nghĩa cột SQL) với một ý nghĩa cụ thể.


3
Vâng, vâng, theo dõi các lớp con. Nhưng tại sao bạn lại muốn điều đó? Ví dụ của bạn chỉ là ẩn cho register_music_file (Mp3File, ['.mp3']), và cách rõ ràng là dễ đọc hơn và dễ bảo trì hơn. Đây là một ví dụ về những trường hợp tồi tệ mà tôi đang nói đến.
Ali Afshar

Về trường hợp ORM, bạn đang nói về cách dựa trên lớp để xác định bảng, hoặc kính đo trên các đối tượng được ánh xạ. Bởi vì SQLAlchemy có thể (đúng) ánh xạ tới bất kỳ lớp nào (và tôi giả định rằng nó không sử dụng siêu kính cho hoạt động đó).
Ali Afshar

10
Tôi thích kiểu khai báo hơn, thay vì yêu cầu các phương thức đăng ký bổ sung cho mọi lớp con - tốt hơn nếu mọi thứ được gói gọn trong một vị trí.
Brian

Đối với sqlalchemy, tôi chủ yếu nghĩ đến lớp khai báo, vì vậy có lẽ sqlobject là một ví dụ tốt hơn. Tuy nhiên, các kính đo được sử dụng nội bộ cũng là ví dụ về việc giải thích lại tương tự các thuộc tính cụ thể để khai báo ý nghĩa.
Brian

2
Xin lỗi, một trong những ý kiến ​​của tôi đã bị lạc trong kịch bản SO timeout. Tôi thấy các lớp để khai báo gần như là một điều ghê tởm. Tôi biết mọi người yêu thích nó, và đó là hành vi được chấp nhận. Nhưng (từ kinh nghiệm) tôi biết nó không thể sử dụng được trong tình huống bạn muốn UN-khai báo mọi thứ. Việc hủy đăng ký một lớp học rất khó .
Ali Afshar

17

Hãy bắt đầu với câu nói kinh điển của Tim Peter:

Kính đeo kính là phép thuật sâu xa hơn 99% người dùng phải lo lắng. Nếu bạn tự hỏi liệu bạn có cần chúng hay không thì bạn không cần (những người thực sự cần chúng biết chắc chắn rằng họ cần chúng và không cần giải thích lý do tại sao). Tim Peters (clp bài 2002-12-22)

Phải nói rằng, tôi đã (định kỳ) chạy qua các mục đích sử dụng thực sự của kính đeo tay. Điều mà bạn nghĩ đến là ở Django, nơi tất cả các mô hình của bạn đều kế thừa từ các mô hình.Model. Đến lượt mình, model.Model thực hiện một số phép thuật nghiêm trọng để bọc các mô hình DB của bạn bằng sự tốt đẹp ORM của Django. Điều kỳ diệu đó xảy ra nhờ kính thiên thạch. Nó tạo ra tất cả các cách thức của các lớp ngoại lệ, lớp người quản lý, v.v.

Xem django / db / models / base.py, class ModelBase () để biết phần đầu của câu chuyện.


Vâng, vâng, tôi thấy vấn đề. Tôi không thắc mắc "làm thế nào" hay "tại sao" sử dụng kính đeo, tôi tự hỏi "ai" và "cái gì". ORM là một trường hợp phổ biến ở đây tôi thấy. Thật không may ORM của Django khá kém so với SQLAlchemy có ít ma thuật hơn. Phép thuật là không tốt, và kính thiên văn thực sự không cần thiết cho việc này.
Ali Afshar

9
Đã từng đọc câu nói của Tim Peters trong quá khứ, thời gian đã cho thấy rằng tuyên bố của anh ấy khá vô ích. Không phải cho đến khi nghiên cứu kính đo Python ở đây trên StackOverflow, người ta mới biết cách triển khai chúng. Sau khi buộc bản thân phải học cách viết và sử dụng kính đeo, khả năng của họ đã khiến tôi kinh ngạc và hiểu rõ hơn về cách Python thực sự hoạt động. Các lớp có thể cung cấp mã có thể sử dụng lại và kính đo có thể cung cấp các cải tiến có thể sử dụng lại cho các lớp đó.
Noctis Skytower

6

Metaclasses có thể hữu ích cho việc xây dựng Ngôn ngữ dành riêng cho miền bằng Python. Ví dụ cụ thể là cú pháp khai báo của Django, SQLObject của schemata cơ sở dữ liệu.

Một ví dụ cơ bản về Metaclass bảo thủ của Ian Bicking:

Kính đo mà tôi đã sử dụng chủ yếu để hỗ trợ một kiểu lập trình khai báo. Ví dụ: hãy xem xét một lược đồ xác thực:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

Một số kỹ thuật khác: Thành phần để xây dựng DSL bằng Python (pdf).

Chỉnh sửa (bởi Ali): Một ví dụ về việc làm điều này bằng cách sử dụng các bộ sưu tập và cá thể là những gì tôi muốn. Thực tế quan trọng là các ví dụ, cung cấp cho bạn nhiều quyền lực hơn và loại bỏ lý do để sử dụng kính đo. Cần lưu ý thêm rằng ví dụ của bạn sử dụng hỗn hợp các lớp và cá thể, điều này chắc chắn là một dấu hiệu cho thấy bạn không thể chỉ làm tất cả với kính đo. Và tạo ra một cách thực hiện không thống nhất.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

Nó không hoàn hảo, nhưng gần như không có ma thuật, không cần kính đo và cải thiện tính đồng nhất.


Cảm ơn vì điều đó. Đây là một ví dụ rất tốt về trường hợp sử dụng mà tôi nghĩ là không cần thiết, xấu xí và không thể quản lý được, sẽ đơn giản hơn dựa trên một thể hiện tập hợp đơn giản (với các tập hợp lồng nhau theo yêu cầu).
Ali Afshar

1
@Ali A: chúng tôi hoan nghênh bạn cung cấp một ví dụ cụ thể về so sánh song song giữa cú pháp khai báo qua metaclasses và cách tiếp cận dựa trên phiên bản thu thập đơn giản.
jfs

@Ali A: bạn có thể chỉnh sửa câu trả lời của tôi tại chỗ để thêm ví dụ về kiểu bộ sưu tập.
jfs

Ok, xong rồi. Xin lỗi vì tôi hơi vội hôm nay, nhưng sẽ cố gắng trả lời bất kỳ câu hỏi nào sau / ngày mai. Kỳ nghỉ vui vẻ!
Ali Afshar

2
Ví dụ thứ hai là xấu vì bạn phải gắn cá thể xác thực với tên của chúng. Một cách tốt hơn một chút là sử dụng từ điển thay vì danh sách, nhưng sau đó, trong các lớp python chỉ là đường cú pháp cho từ điển, vậy tại sao không sử dụng các lớp? Bạn cũng nhận được xác thực tên miễn phí bởi vì python babes không thể chứa khoảng trắng hoặc ký tự đặc biệt mà một chuỗi có thể.
Lie Ryan

6

Một mô hình sử dụng metaclass hợp lý là thực hiện điều gì đó một lần khi một lớp được xác định thay vì lặp lại bất cứ khi nào cùng một lớp được khởi tạo.

Khi nhiều lớp chia sẻ cùng một hành vi đặc biệt, việc lặp lại __metaclass__=Xrõ ràng là tốt hơn việc lặp lại mã mục đích đặc biệt và / hoặc giới thiệu các lớp cha chia sẻ đặc biệt.

Nhưng ngay cả khi chỉ có một lớp đặc biệt và không có phần mở rộng có thể thấy trước, __new____init__của một siêu lớp là một cách dễ dàng hơn để khởi tạo các biến lớp hoặc dữ liệu toàn cục khác so với việc trộn mã mục đích đặc biệt, bình thường defvà các classcâu lệnh trong phần thân định nghĩa lớp.


5

Lần duy nhất tôi sử dụng metaclasses trong Python là khi viết trình bao bọc cho API Flickr.

Mục tiêu của tôi là quét trang web api của flickr và tạo động một hệ thống phân cấp lớp hoàn chỉnh để cho phép truy cập API bằng các đối tượng Python:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

Vì vậy, trong ví dụ đó, vì tôi đã tạo toàn bộ API Python Flickr từ trang web, nên tôi thực sự không biết các định nghĩa lớp trong thời gian chạy. Có thể tạo động các kiểu rất hữu ích.


2
Bạn có thể tự động tạo các loại mà không cần sử dụng kính đo. >>> trợ giúp (loại)
Ali Afshar

8
Ngay cả khi bạn không nhận thức được điều đó, bạn đang sử dụng kính đeo tay. loại là một siêu thủy tinh, trên thực tế là loại phổ biến nhất. :-)
Veky

5

Tôi đã nghĩ điều tương tự mới hôm qua và hoàn toàn đồng ý. Theo ý kiến ​​của tôi, những phức tạp trong mã gây ra bởi nỗ lực làm cho nó dễ khai báo hơn làm cho cơ sở mã khó bảo trì hơn, khó đọc hơn và ít phức tạp hơn. Nó cũng thường yêu cầu rất nhiều copy.copy () ing (để duy trì tính kế thừa và sao chép từ lớp này sang lớp khác) và có nghĩa là bạn phải tìm ở nhiều nơi để xem điều gì đang xảy ra (luôn nhìn từ metaclass trở lên) điều này đi ngược lại với hạt trăn cũng vậy. Tôi đã chọn qua mã formencode và mã sqlalchemy để xem liệu kiểu khai báo như vậy có xứng đáng hay không và rõ ràng là không. Kiểu như vậy nên được để lại cho các bộ mô tả (chẳng hạn như thuộc tính và phương thức) và dữ liệu bất biến. Ruby có hỗ trợ tốt hơn cho các kiểu khai báo như vậy và tôi rất vui vì ngôn ngữ python cốt lõi không đi theo lộ trình đó.

Tôi có thể thấy việc sử dụng chúng để gỡ lỗi, thêm một siêu kính vào tất cả các lớp cơ sở của bạn để có thêm thông tin. Tôi cũng thấy chúng chỉ được sử dụng trong (rất) các dự án lớn để loại bỏ một số mã soạn sẵn (nhưng không rõ ràng). Ví dụ: sqlalchemy sử dụng chúng ở nơi khác, để thêm một phương thức tùy chỉnh cụ thể vào tất cả các lớp con dựa trên một giá trị thuộc tính trong định nghĩa lớp của chúng, ví dụ như ví dụ đồ chơi

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

có thể có một siêu kính tạo ra một phương thức trong lớp đó với các thuộc tính đặc biệt dựa trên "hello" (giả sử một phương thức đã thêm "hello" vào cuối một chuỗi). Nó có thể tốt cho khả năng bảo trì để đảm bảo rằng bạn không phải viết một phương thức trong mọi lớp con bạn tạo, thay vào đó tất cả những gì bạn phải xác định là method_maker_value.

Tuy nhiên, nhu cầu này rất hiếm và chỉ cắt giảm một chút thao tác nhập nên nó không thực sự đáng xem xét trừ khi bạn có một cơ sở mã đủ lớn.


5

Bạn không bao giờ nhất thiết phải sử dụng metaclass, vì bạn luôn có thể tạo một lớp thực hiện những gì bạn muốn bằng cách sử dụng kế thừa hoặc tổng hợp của lớp mà bạn muốn sửa đổi.

Điều đó nói rằng, có thể rất tiện dụng trong Smalltalk và Ruby để có thể sửa đổi một lớp hiện có, nhưng Python không thích làm điều đó trực tiếp.

Có một bài viết tuyệt vời của DeveloperWorks về siêu phân loại trong Python có thể hữu ích. Các bài viết trên Wikipedia cũng là khá tốt.


1
Bạn cũng không cần các đối tượng để thực hiện lập trình hướng đối tượng — bạn có thể làm điều đó với các hàm lớp đầu tiên. Vì vậy, bạn không cần phải sử dụng các đối tượng. Nhưng họ ở đó để thuận tiện. Vì vậy, tôi không chắc bạn đang cố gắng đưa ra điểm nào trong đoạn đầu tiên.
Tyler Crompton

1
Nhìn lại câu hỏi.
Charlie Martin

4

Một số thư viện GUI gặp sự cố khi nhiều luồng cố gắng tương tác với chúng. tkinterlà một trong những ví dụ như vậy; và trong khi người ta có thể xử lý vấn đề một cách rõ ràng với các sự kiện và hàng đợi, thì việc sử dụng thư viện theo cách hoàn toàn bỏ qua vấn đề có thể đơn giản hơn nhiều. Kìa - sự kỳ diệu của kính thiên thạch.

Khả năng tự động viết lại toàn bộ thư viện một cách liền mạch để nó hoạt động đúng như mong đợi trong một ứng dụng đa luồng có thể cực kỳ hữu ích trong một số trường hợp. Các safetkinter mô-đun nào đó với sự giúp đỡ của một metaclass cung cấp bởi threadbox mô-đun - sự kiện và hàng đợi không cần thiết.

Một khía cạnh gọn gàng của threadboxnó là nó không quan tâm lớp nó nhân bản. Nó cung cấp một ví dụ về cách tất cả các lớp cơ sở có thể được chạm vào bởi một siêu kính nếu cần. Một lợi ích nữa đi kèm với kính đo là chúng cũng chạy trên các lớp kế thừa. Các chương trình tự viết - tại sao không?


4

Trường hợp sử dụng hợp pháp duy nhất của metaclass là ngăn các nhà phát triển tọc mạch khác chạm vào mã của bạn. Sau khi một nhà phát triển tọc mạch thành thạo kính thiên văn và bắt đầu chọc phá chiếc kính của bạn, hãy chuyển sang một hoặc hai cấp độ khác để ngăn chặn chúng. Nếu điều đó không hiệu quả, hãy bắt đầu sử dụngtype.__new__ hoặc có lẽ một số lược đồ sử dụng một siêu kính đệ quy.

(viết lưỡi vào má, nhưng tôi đã thấy kiểu làm khó nghe này được thực hiện. Django là một ví dụ hoàn hảo)


7
Tôi không chắc động lực ở Django có giống nhau không.
Ali Afshar

3

Metaclasses không thay thế lập trình! Chúng chỉ là một thủ thuật có thể tự động hóa hoặc thực hiện một số nhiệm vụ thanh lịch hơn. Một ví dụ điển hình về điều này là thư viện đánh dấu cú pháp Pypts . Nó có một lớp gọi làRegexLexer cho phép người dùng định nghĩa một tập hợp các quy tắc lexing dưới dạng các biểu thức chính quy trên một lớp. Metaclass được sử dụng để biến các định nghĩa thành một trình phân tích cú pháp hữu ích.

Chúng giống như muối; nó dễ sử dụng quá nhiều.


Theo ý kiến ​​của tôi, trường hợp Pyamonds đó là không cần thiết. Tại sao không chỉ có một bộ sưu tập đơn giản như một dict, tại sao buộc một lớp phải làm điều này?
Ali Afshar

4
Bởi vì một lớp tốt đẹp bao hàm ý tưởng về Lexer và có các phương thức hữu ích khác như đoán_filename (), v.v.
Benjamin Peterson

3

Cách tôi sử dụng kính thiên văn là cung cấp một số thuộc tính cho các lớp. Lấy ví dụ:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

sẽ đặt thuộc tính name trên mọi lớp sẽ có metaclass được đặt để trỏ đến NameClass.


5
Có, điều này hoạt động. Bạn cũng có thể sử dụng lớp cha, lớp này ít nhất là rõ ràng và có thể theo dõi được trong mã. Không quan tâm, bạn đã sử dụng cái này để làm gì?
Ali Afshar

2

Đây là một công dụng nhỏ, nhưng ... một thứ mà tôi thấy metaclasses hữu ích là gọi một hàm bất cứ khi nào một lớp con được tạo. Tôi đã mã hóa điều này thành một siêu lớp tìm kiếm một __initsubclass__thuộc tính: bất cứ khi nào một lớp con được tạo, tất cả các lớp cha xác định phương thức đó đều được gọi với __initsubclass__(cls, subcls). Điều này cho phép tạo một lớp cha sau đó đăng ký tất cả các lớp con với một số đăng ký toàn cục, chạy kiểm tra bất biến trên các lớp con bất cứ khi nào chúng được xác định, thực hiện các hoạt động ràng buộc muộn, v.v. tất cả mà không cần phải gọi các hàm hoặc để tạo ra metaclasses tùy chỉnh mà thực hiện từng nhiệm vụ riêng biệt này.

Xin lưu ý bạn, tôi dần dần nhận ra điều kỳ diệu tiềm ẩn của hành vi này là hơi không mong muốn, vì thật bất ngờ nếu nhìn vào định nghĩa lớp ngoài ngữ cảnh ... và vì vậy tôi đã tránh sử dụng giải pháp đó cho bất cứ điều gì nghiêm trọng bên cạnh khởi tạo một __superthuộc tính cho mỗi lớp và cá thể.


1

Gần đây, tôi đã phải sử dụng siêu kính để giúp xác định một cách khai báo mô hình SQLAlchemy xung quanh bảng cơ sở dữ liệu được điền dữ liệu Điều tra dân số Hoa Kỳ từ http://census.ire.org/data/bulkdata.html

IRE cung cấp các khung cơ sở dữ liệu cho các bảng dữ liệu điều tra dân số, tạo các cột số nguyên theo quy ước đặt tên từ Cục điều tra dân số p012015, p012016, p012017, v.v.

Tôi muốn a) có thể truy cập các cột này bằng model_instance.p012017cú pháp, b) khá rõ ràng về những gì tôi đang làm và c) không phải xác định rõ ràng hàng tá trường trên mô hình, vì vậy tôi đã phân lớp SQLAlchemy's DeclarativeMetađể lặp qua một phạm vi các cột và tự động tạo các trường mô hình tương ứng với các cột:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

Sau đó, tôi có thể sử dụng siêu kính này cho định nghĩa mô hình của mình và truy cập các trường được liệt kê tự động trên mô hình:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...

1

Dường như có một cách sử dụng hợp pháp được mô tả ở đây - Viết lại các Docstrings Python bằng Metaclass.


0

Tôi đã phải sử dụng chúng một lần cho trình phân tích cú pháp nhị phân để dễ sử dụng hơn. Bạn xác định một lớp thông báo với các thuộc tính của các trường hiện diện trên dây. Chúng cần được sắp xếp theo cách chúng được khai báo để xây dựng định dạng dây cuối cùng từ nó. Bạn có thể làm điều đó với metaclasses, nếu bạn sử dụng một không gian tên có thứ tự. Trên thực tế, nó trong các ví dụ cho Metaclasses:

https://docs.python.org/3/reference/datamodel.html#metaclass-example

Nhưng nói chung: Hãy đánh giá rất cẩn thận, nếu bạn thực sự cần sự phức tạp thêm của kính đo.


0

câu trả lời từ @Dan Gittik thật tuyệt

các ví dụ ở cuối có thể làm rõ nhiều điều, tôi đã đổi nó thành python 3 và đưa ra một số giải thích:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • mọi thứ đều là đối tượng, vì vậy lớp là đối tượng
  • đối tượng lớp được tạo bởi metaclass
  • tất cả lớp được kế thừa từ kiểu là siêu kính
  • metaclass có thể kiểm soát việc tạo lớp
  • metaclass cũng có thể kiểm soát việc tạo metaclass (vì vậy nó có thể lặp lại mãi mãi)
  • đây là lập trình siêu hình ... bạn có thể kiểm soát hệ thống loại vào thời gian chạy
  • một lần nữa, mọi thứ đều là đối tượng, đây là một hệ thống thống nhất, gõ kiểu tạo và kiểu tạo phiên bản

0

Một trường hợp sử dụng khác là khi bạn muốn có thể sửa đổi các thuộc tính cấp lớp và đảm bảo rằng nó chỉ ảnh hưởng đến đối tượng trong tầm tay. Trong thực tế, điều này ngụ ý "hợp nhất" các giai đoạn của metaclass và các khởi tạo lớp, do đó dẫn đến việc bạn chỉ xử lý các trường hợp lớp thuộc loại (duy nhất) của riêng chúng.

Tôi cũng phải làm điều đó khi (vì lo ngại về tính dễ đọctính đa hình ) chúng tôi muốn xác định động property s mà các giá trị trả về (có thể) là kết quả của các phép tính dựa trên các thuộc tính cấp cá thể (thường thay đổi), điều này chỉ có thể được thực hiện ở cấp lớp , tức là sau khi khởi tạo siêu kính và trước khi khởi tạo lớp.

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.