Re: [Pyobjc-dev] TypeError: cannot change a method
Brought to you by:
ronaldoussoren
From: Daniel M. <mil...@gm...> - 2009-07-18 13:49:49
|
On Jul 17, 2009, at 12:53 AM, Ronald Oussoren wrote: > On 17 Jul, 2009, at 3:09, Daniel Miller wrote: >> >> >> FWIW, it seems to be possible to replace a method directly on the >> PyObjC type. For example: >> >> # this works >> fake = descriptor_mock() >> NSDocument.removeWindowController_ = fake >> >> Assuming the 'fake' object implements the descriptor protocol, the >> fake/mocked method will get called as expected. >> But it then it fails when trying to restore the original method: >> >> NSDocument.removeWindowController_ = original >> # TypeError: Assigning native selectors is not supported >> >> However, this seems to restore the original: >> >> del NSDocument.removeWindowController_ >> >> Strange... while I may be able to use this approach, it has the >> disadvantage of replacing the method on ALL instances of the type >> rather than on just a the instance that I'm testing--I may need to >> rewrite some tests to work with that restriction, but it probably >> wouldn't be the end of the world. > > You're not going to like this, but the behaviour you describe here > sounds like a bug. In particular, it is possible to overwrite an > existing method using a new function object but it should not be > possible to overwrite it with something else. Removing the fake > descriptor should also not result in revival of the original method > because a replacement method should result in a changed method in > the ObjC runtime. Does a descriptor (i.e. an object with a __get__() member) count as a function object? Also, since it is possible to overwrite a method with a function, wouldn't it make sense to provide some way to restore it to its original state? That should be all I need. I can understand the motivation to make it illegal to overwrite a method on an instance with a non-callable object. However, could the restriction be relaxed to allow a callable object (function, object with __call__, etc.) to overwrite a method on an instance? As far as I know any attempt to replace a method on an instance raises an error right now. > I need to think about your testing needs, if it is possible to > support those and if so how. If there is some way to replace a method with some other callable, and then restore to the original state, I think that's all I need. I can even deal if it can only be done on the type (not instance), but I think my above proposal to allow overwriting an instance method with some other callable would probably make sense, while still protecting the newbies from overwriting methods unintentionally. > A number of limititations in PyObjC, such as this one, are caused by > the need to interact with the Objective-C runtime. In particular > "objc.selector" objects for native methods are basicly references to > a named selector on the ObjC class, they do not contain a reference > to the code that will be called when you call the method. That's why > it is not possible to assign objc.selector instances to a class > attribute. So you're saying the name to which the selector is bound is the important part, not the contents of the selector itself? That's interesting... That should not cause a problem as long as there is some way to restore the selector to its original state after it has been overwritten with a mock method. Maybe PyObjCTools could gain a special setattr-like function that could be used to overwrite selectors. This function would carry a warning that it should only be used by people who know what they are doing. It's more kludgy than normal for python, but in this case PyObjC violates the normal python conventions, so a kludge may be justified. > BTW. How does your mocking library deal with other native types: > > >>> l = list() > >>> l.append = 42 > Traceback (most recent call last): > File "<stdin>", line 1, in <module> > AttributeError: 'list' object attribute 'append' is read-only > >>> I have not needed to mock methods on built-in types, such as list.append(), because they do not generally have wide-reaching side affects (unlike NSWindowController.window() for example). In a case where I really did not want to mutate a list, I would mock/replace the entire list instance for the duration of the test (I do this quite often). In a framework such as PyObjC, with unconventional write restrictions and where many objects are created and/or owned privately by the framework, this type of replacement becomes difficult or impossible. Hence the need to be able to replace methods with far- reaching side affects. If I can't do that then it becomes nearly impossible to write tests for code that interacts with the GUI. > Or more interesting, how does it deal with special methods like > __getitem__, those always get resolved through the class: > > >>> class List (list): pass > ... > >>> l = List() > >>> l.append(42) > >>> l[0] > 42 > >>> l.__getitem__ = 42 > >>> l[0] > 42 > >>> Well, in this case I would replace List.__getitem__ (i.e. replace the method on the subclass) which is completely legal and possible. PyObjC types do not allow this. They are more restrictive than the python built-in types because they do not allow me to overwrite a method of my own subtype. > As you can see setting __getitem__ on an instance has no effect on > which special method is actually used. Yes, but if you replace __getitem__ on the subtype, then it works. Special methods need to be handled specially. For my test framework, PyObjC methods are one class of special method. PyObjC 2 has made them harder to deal with, but it's not impossible (yet). Although it sounds like you might be making it harder (if the scenario I pointed out above is actually a bug). ~ Daniel |