10

From Programming Language Pragmatics, by Scott

Both Python and Ruby are more flexible than PHP or more traditional object- oriented languages regarding the contents (members) of a class. New fields can be added to a Python object simply by assigning to them: my_object.new_field = value. The set of methods, however, is fixed when the class is first defined. In Ruby only methods are visible outside a class (“put” and “get” methods must be used to access fields), and all methods must be explicitly declared. It is possible, however, to modify an existing class declaration, adding or overriding methods. One can even do this on an object-by-object basis. As a result, two objects of the same class may not display the same behavior.

What does "The set of methods, however, is fixed when the class is first defined" mean?

I seem to have found a counterexample:

>>> class E:
...     pass
... 
>>> E.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'E' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'E' objects>})
>>> def myfun():
...     pass
... 
>>> E.mf=myfun
>>> E.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'E' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'E' objects>, 'mf': <function myfun at 0x7f6561daba60>})
Cœur
  • 37,241
  • 25
  • 195
  • 267
Tim
  • 1
  • 141
  • 372
  • 590
  • 1
    Note that `mf` is not actually a method; it is simply a function-valued attribute. – chepner Sep 05 '17 at 13:26
  • @chepner Can you explain what that means? – Josh Lee Sep 05 '17 at 13:29
  • Possibly not :) I seem to forget what the subtle distinction involved is. – chepner Sep 05 '17 at 13:30
  • 5
    It is a class method, but an instance method can be added exactly the same way. So I guess the short answer is "Scott was wrong". – Błotosmętek Sep 05 '17 at 13:32
  • Assigning a function to a *class* works fine for defining a method, which is then available to all instances (whether they were created before or after the class was patched). Assigning a function to an *instance* does not work. – chepner Sep 05 '17 at 13:34
  • I think the quote is slightly misworded, since it goes on to contradict itself by saying you can add methods to a class. – chepner Sep 05 '17 at 13:35
  • It's possible that the distinction intended is as simple as being able to write `class Foo … end` at any time in Ruby to monkeypatch without having to use a different syntax. – Josh Lee Sep 05 '17 at 13:38
  • 3
    Understanding the descriptor protocol goes a long way towards understanding how methods work. In brief, `function` objects have a `__get__` method that is invoked when the function is the value of a class attribute, which returns a `method` object. – chepner Sep 05 '17 at 13:38

2 Answers2

7

Like shown in the question: it's trivial to add a function to a class object that is behaving just like any method would:

def fake_method(self,idx):
    print(self, idx)

class MyClass(object):
    pass

MyClass.new_method = fake_method

n = MyClass()
n.new_method(10)
# <__main__.MyClass object at 0x000001BBA6E90860> 10

You can also add "method"-like "callable attributes" to an instance:

import types

def fake_method(self,idx):
    print(self, idx)

class MyClass(object):
    pass

n = MyClass()
n.new_method = types.MethodType(fake_method, n)

n.new_method(10)
# <__main__.MyClass object at 0x000001BBA6E9C2E8> 10

The types.MethodType is needed here because it would otherwise behave like a staticmethod.

My summary: Either I'm missing some crucial point of the quote or it's wrong.

MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • Thanks. Reading about your comment on `types.MethodType` makes me wonder about another question https://stackoverflow.com/questions/45655463/differences-between-methods-of-a-class-which-are-function-and-which-are-boun – Tim Sep 05 '17 at 14:40
0

At instance level, one can modify fields directly as mentioned.

At class/module level, there is an unofficial mechanics called monkey patching. Basically you would obtain a reference (?) to the class/module, and override the function.

However, it is not clear to me if static methods can be overridden. And the modification is only valid in local scope where monkey patches are done, I believe. Therefore you should not expect other scripts that import the same class/module to share the monkey patches, unless they also apply the patches explicitly. Of course, you can also create a wrapper module.

I do not think this is much advocated by the Python community. It is quite difficult to keep the patches in track over a long time.

More info: What is a monkey patch?

Patrick the Cat
  • 2,138
  • 1
  • 16
  • 33
  • Hm. I'd say that monkey-patching a _module_ carries risk of getting out of synch, because importing module members (that are not themselves modules) into other modules is entirely possible (though discouraged by some style guides). But in the context of methods, the changes are always globally visible. – mwchase Sep 05 '17 at 13:52