2011-12-26 24 views
8

Tôi có một lớp học cần thực hiện một số phép thuật với mọi toán tử, như __add__, __sub__ v.v.Tra cứu toán tử đánh chặn trên metaclass

Thay vì tạo mỗi hàm trong lớp, tôi có một metaclass xác định mọi toán tử trong môđun toán tử.

import operator 
class MetaFuncBuilder(type): 
    def __init__(self, *args, **kw): 
     super().__init__(*args, **kw) 
     attr = '__{0}{1}__' 
     for op in (x for x in dir(operator) if not x.startswith('__')): 
      oper = getattr(operator, op) 

      # ... I have my magic replacement functions here 
      # `func` for `__operators__` and `__ioperators__` 
      # and `rfunc` for `__roperators__` 

      setattr(self, attr.format('', op), func) 
      setattr(self, attr.format('r', op), rfunc) 

Phương pháp này hoạt động tốt, nhưng tôi nghĩ Sẽ tốt hơn nếu tôi chỉ tạo toán tử thay thế khi cần.

Lookup các nhà khai thác nên được trên metaclass vì x + 1 được thực hiện như type(x).__add__(x,1) thay vì x.__add__(x,1), nhưng nó không nhận được đánh bắt bởi __getattr__ cũng không __getattribute__ phương pháp.

Điều đó không làm việc:

class Meta(type): 
    def __getattr__(self, name): 
      if name in ['__add__', '__sub__', '__mul__', ...]: 
       func = lambda:... #generate magic function 
       return func 

Ngoài ra, kết quả là "chức năng" phải là một phương pháp liên kết với các trường hợp sử dụng.

Bất kỳ ý tưởng nào về cách tôi có thể đánh chặn tra cứu này? Tôi không biết liệu tôi có muốn làm gì không.


Đối với những câu hỏi tại sao tôi cần phải loại điều này, hãy kiểm tra mã đầy đủ here. Đó là công cụ để tạo các chức năng (chỉ để cho vui) có thể hoạt động thay thế cho lambda s.

Ví dụ:

>>> f = FuncBuilder() 
>>> g = f ** 2 
>>> g(10) 
100 
>>> g 
<var [('pow', 2)]> 

Chỉ cần cho các hồ sơ, tôi không muốn biết một cách khác để làm điều tương tự (Tôi sẽ không tuyên bố tất cả các nhà khai thác duy nhất trên lớp ... đó sẽ là nhàm chán và cách tiếp cận tôi đã làm việc khá tốt :). Tôi muốn biết cách đánh chặn tra cứu thuộc tính từ toán tử.

+0

"Tôi có một lớp học cần thực hiện một số phép thuật với mọi nhà điều hành" - Tại sao? Có vẻ như bạn đang sủa một cây rất phức tạp ... –

+0

@LennartRegebro Tôi đang viết một trình tạo hàm bằng cách sử dụng toán tử trên một số đối tượng. 'f = FuncBuilder(); g = f ** 2 + 1; g (10) == 101'. Nó không phải là một cái gì đó rất hữu ích (rất nhiều các cuộc gọi chức năng), nhưng có phần thú vị để sử dụng: D – JBernardo

+0

@LennartRegebro Tôi đã đăng toàn bộ mã. – JBernardo

Trả lời

5

Một số ma thuật đen cho phép của bạn đạt được mục tiêu của bạn:

operators = ["add", "mul"] 

class OperatorHackiness(object): 
    """ 
    Use this base class if you want your object 
    to intercept __add__, __iadd__, __radd__, __mul__ etc. 
    using __getattr__. 
    __getattr__ will called at most _once_ during the 
    lifetime of the object, as the result is cached! 
    """ 

    def __init__(self): 
    # create a instance-local base class which we can 
    # manipulate to our needs 
    self.__class__ = self.meta = type('tmp', (self.__class__,), {}) 


# add operator methods dynamically, because we are damn lazy. 
# This loop is however only called once in the whole program 
# (when the module is loaded) 
def create_operator(name): 
    def dynamic_operator(self, *args): 
    # call getattr to allow interception 
    # by user 
    func = self.__getattr__(name) 
    # save the result in the temporary 
    # base class to avoid calling getattr twice 
    setattr(self.meta, name, func) 
    # use provided function to calculate result 
    return func(self, *args) 
    return dynamic_operator 

for op in operators: 
    for name in ["__%s__" % op, "__r%s__" % op, "__i%s__" % op]: 
    setattr(OperatorHackiness, name, create_operator(name)) 


# Example user class 
class Test(OperatorHackiness): 
    def __init__(self, x): 
    super(Test, self).__init__() 
    self.x = x 

    def __getattr__(self, attr): 
    print "__getattr__(%s)" % attr 
    if attr == "__add__": 
     return lambda a, b: a.x + b.x 
    elif attr == "__iadd__": 
     def iadd(self, other): 
     self.x += other.x 
     return self 
     return iadd 
    elif attr == "__mul__": 
     return lambda a, b: a.x * b.x 
    else: 
     raise AttributeError 

## Some test code: 

a = Test(3) 
b = Test(4) 

# let's test addition 
print a + b # this first call to __add__ will trigger 
      # a __getattr__ call 
print a + b # this second call will not! 

# same for multiplication 
print a * b 
print a * b 

# inplace addition (getattr is also only called once) 
a += b 
a += b 
print a.x # yay! 

Output

__getattr__(__add__) 
7 
7 
__getattr__(__mul__) 
12 
12 
__getattr__(__iadd__) 
11 

Bây giờ bạn có thể sử dụng mẫu mã thứ hai của bạn theo nghĩa đen bởi kế thừa từ lớp OperatorHackiness cơ sở của tôi.Bạn thậm chí có được một lợi ích bổ sung: __getattr__ sẽ chỉ được gọi một lần cho mỗi trường hợp và toán tử và không có lớp đệ quy bổ sung nào liên quan đến bộ nhớ đệm. Chúng tôi giải thích vấn đề các cuộc gọi phương thức chậm hơn so với tra cứu phương pháp (như Paul Hankin đã nhận thấy chính xác).

CHÚ Ý: Vòng lặp để thêm các phương thức toán tử chỉ được thực hiện một lần trong toàn bộ chương trình của bạn, vì vậy việc chuẩn bị có phí cố định trong phạm vi mili giây.

+0

Vâng, có vẻ như vòng lặp 'for' của bạn đang thêm tất cả các toán tử vào lớp (xem mã của tôi, tôi cũng làm điều đó). Tôi muốn ** không ** có chúng :). BTW, tôi nghĩ nó đã được cải thiện rồi. – JBernardo

+0

@JBernardo: Hãy nhìn lại. Nó hoạt động hoàn toàn khác với mã của bạn. Những gì được thêm vào không phải là các hàm toán tử được tạo ra mà chỉ là các trình bao bọc cạn xung quanh một cuộc gọi '__getattr__'. Điều này là cần thiết, bởi vì, như bạn đã nói, bạn không thể chặn các cuộc gọi phương thức đó bằng cách sử dụng một hàm '__getattr__' tùy chỉnh. Vì vòng lặp chỉ được thực hiện _once trong toàn bộ chương trình của bạn và số lượng toán tử là hữu hạn, nên nó mất phí trên không trong phạm vi mili giây. Về cơ bản, đây là một hack để cho phép bạn sử dụng '__getattr__' để chặn các toán tử giống như bất kỳ phương thức nào khác (đó là chính xác những gì bạn yêu cầu). –

+0

Tôi hiểu mã của bạn (bạn cũng nên thêm những nhận xét này vào câu trả lời), nhưng những gì bạn làm là: 'x + y -> x .__ add__ -> x .__ getattr __ ('__ add __')'. Đó là một ý tưởng thú vị, nhưng có vẻ như không có các nhà khai thác là không thể nào đó. – JBernardo

1

Dường như bạn đang làm mọi thứ quá phức tạp. Bạn có thể định nghĩa một lớp mixin và kế thừa từ nó. Điều này đơn giản hơn việc sử dụng metaclasses và sẽ chạy nhanh hơn sử dụng __getattr__.

class OperatorMixin(object): 
    def __add__(self, other): 
     return func(self, other) 
    def __radd__(self, other): 
     return rfunc(self, other) 
    ... other operators defined too 

Sau đó, mỗi lớp bạn muốn có các toán tử này, kế thừa từ OperatorMixin.

class Expression(OperatorMixin): 
    ... the regular methods for your class 

Tạo các phương pháp điều hành khi họ đang cần không phải là một ý tưởng tốt: __getattr__ là chậm so với tra cứu phương pháp thông thường, và kể từ khi phương pháp được lưu trữ một lần (vào các lớp mixin), nó tiết kiệm gần như không có gì .

+0

Vâng, có ít nhất 10 toán tử (cộng với biểu mẫu tại chỗ và đảo ngược) và tôi không muốn viết chúng bằng tay và gọi chính xác cùng một chức năng (thay đổi toán tử) cho mỗi người trong số họ. – JBernardo

+0

Ý tưởng của tôi bây giờ là * chỉ * tạo 'func' hoặc' rfunc' khi toán tử được gọi. – JBernardo

+0

Điều gì sẽ tạo ra các chức năng lười biếng cho bạn? –

3

Vấn đề ở chỗ là Python tra cứu __xxx__ phương pháp trên lớp của đối tượng, không phải trên đối tượng - và nếu không tìm thấy, nó không rơi trở lại __getattr__ cũng không __getattribute__.

Cách duy nhất để chặn cuộc gọi như vậy là có phương thức đã có. Nó có thể là một chức năng sơ khai, như trong câu trả lời của Niklas Baumstark, hoặc nó có thể là chức năng thay thế chính thức; một trong hai cách, tuy nhiên, có phải là một cái gì đó đã có hoặc bạn sẽ không thể chặn các cuộc gọi đó.

Nếu bạn đọc kỹ, bạn sẽ nhận thấy rằng yêu cầu của bạn về phương pháp cuối cùng bị ràng buộc đối với cá thể không phải là giải pháp khả thi - bạn có thể thực hiện, nhưng Python sẽ không bao giờ gọi nó như Python đang xem lớp của cá thể, không phải là cá thể, cho các phương thức __xxx__. Giải pháp của Niklas Baumstark trong việc tạo ra một lớp temp duy nhất cho mỗi cá thể là gần như bạn có thể đạt được yêu cầu đó.