2011-10-12 33 views
5

Tôi có một lớp chỉ định một tập hợp các chức năng gọi lại (được hiển thị ở đây là cb1cb2). Tôi giữ một bản đồ của những thứ mà tôi muốn gọi sau một số sự kiện.Tham chiếu Python để gọi lại trong từ điển

class Foo: 
    cb1 = None 
    cb2 = None 

    def test(self, input): 
     for (name, callback) in map: 
      if name == input: 
       if callback: callback() 
       ... 

    map = {'one':cb1, 'two':cb2} 

def mycallback(): 
    print "mycallback()" 

f = Foo() 
f.cb1 = mycallback # Register our callback 
f.test('one')  # Nothing happens 

Bạn có thể phát hiện sự cố không?

gì xảy ra, là khi các lớp được khởi tạo, giá trị của cb1cb2 (trong đó có cả None) được copy vào bản đồ. Vì vậy, ngay cả sau khi người dùng 'đăng ký' cuộc gọi lại (bằng cách gán cho cb1), giá trị trong bản đồ vẫn là None và không có gì được gọi.

Vì không có thứ như 'bằng cách tham chiếu' bằng Python, làm cách nào để khắc phục điều này?

+0

nitpick: tất cả mọi thứ được truyền bằng cách tham khảo 'bằng Python. Nhưng nó bằng cách tham chiếu, không phải bởi * name *: nếu bạn rebind tên cho đối tượng khác, điều đó không cập nhật các tham chiếu khác cho bất kỳ tên nào trỏ đến. –

Trả lời

9

Tại sao không khiến lớp học của bạn xử lý đăng ký một cách rõ ràng?

import collections 

class Foo(object): 
    handlers = None 

    def __init__(self): 
     self.handlers = collections.defaultdict(set) 

    def register(self, event, callback): 
     self.handlers[event].add(callback) 

    def fire(self, event, **kwargs): 
     for handler in self.handlers.get(event, []): 
      handler(**kwargs) 

foo = Foo() 
foo.register('one', mycallback) 
foo.fire('one') 
+0

Bạn nói đúng. Từ điển của tôi thực sự phức tạp hơn tôi đã trình bày - nó có các tham chiếu đến các chức năng phân tích cú pháp và những thứ khác, vì vậy ban đầu tôi đã bỏ qua điều này không phải là một giải pháp tương thích. Nhưng nhìn thấy nó bây giờ, nó là khá rõ ràng là cách tốt nhất để làm điều này. Cảm ơn bạn! –

1

Thêm chức năng đăng ký. Trong Foo lớp:

def register(self, name, cb): self.map[name] = cb 

và thay vì:

f.cb1 = mycallback 

sử dụng:

f.register('one', mycallback) 
+0

Cảm ơn vì điều này - người kia đã đánh bại bạn. BTW, OP của tôi có lỗi đánh máy - tôi đã có 'cb1 = mycallback' thay cho' f.cb1 = mycallback' để bạn có thể chỉnh sửa câu trả lời của mình để phản ánh. –

-1

Ngược lại, mọi thứ đều "bằng cách tham khảo" trong Python. Nhưng bạn đang sao chép tham chiếu đến None vào từ điển của mình và việc thay đổi vị trí ban đầu không làm bất kỳ điều gì với tham chiếu đó. Nếu bạn muốn giữ lại mức độ gián tiếp, thì cách đơn giản nhất là lưu trữ các chuỗi. Nếu tất cả các cuộc gọi lại của bạn là thuộc tính của lớp này, hãy loại bỏ map và chỉ lưu trữ danh sách các tên thuộc tính gọi lại. callback_names = ['cb1', 'cb2'] và sau đó sử dụng getattr(self, callback_name)() để gọi lại cuộc gọi lại. Nếu bạn phải có bản đồ, bạn có thể làm map = {'one': 'cb1', 'two': 'cb2'}.

Bạn cũng có thể làm điều gì đó lạ mắt với các thuộc tính, nhưng điều đó có vẻ không cần thiết phức tạp.

0

Với bộ mô tả đại biểu và một chút thủ thuật thuộc tính.

class Delegate(object): 
    def __get__(self, instance, owner): 
    return instance._cbs.get(self, lambda x: None) 

    def __set__(self, instance, value): 
    if not hasattr(instance, '_cbs'): 
     instance._cbs = {} 
    instance._cbs[self] = value 

    def __delete__(self, instance): 
    if not hasattr(instance, '_cbs'): 
     instance._cbs = {} 
    instance._cbs[self] = lambda x: None 

    def __hash__(self): 
    return id(self) 

class C(object): 
    cb1 = Delegate() 
    map = {'one': 'cb1'} 

    def test(self, cb): 
    getattr(self, self.map[cb])() 

def foo(): 
    print 'bar!' 

c = C() 
c.cb1 = foo 
c.test('one') 
+0

Thông minh, nhưng tôi nghi ngờ người hỏi không thực sự cần một giải pháp phức tạp. –

0

Tại sao bạn cần đặt biến khác để tùy chỉnh gọi lại so với biến thực sự được sử dụng để thực thi? Nếu bạn sử dụng cùng một biến, vấn đề sẽ biến mất.

Với một số đường cú pháp nó có thể trông như thế này:

class CallbackMap(object): 
    pass 

class Foo(object): 
    callbacks = CallbackMap() 

    def test(self, input): 
     callback = getattr(Foo.callbacks, input) 
     if callback: callback() 

# setup defaults 
Foo.callbacks.one = None 
Foo.callbacks.two = some_default_callback 

# customize 
def mycallback(): 
    print "mycallback()" 

f = Foo() 
Foo.callbacks.one = mycallback # Register our callback 
f.test('one') # works 
Các vấn đề liên quan