2

Purely curiosity question:

class Li(list): pass
m, n= Li([1]), Li([2])
def r(*args, **kwargs): raise Exception('hop')
setattr(m, '__iadd__', r)
m += n
print m # [1, 2]
setattr(Li, '__iadd__', r)
m += n

Output:

[1, 2]
Traceback (most recent call last):
  File "C:\...\test_override.py", line 8, in <module>
    m+=n
  File "C:\...\test_override.py", line 3, in r
    def r(*args, **kwargs): raise Exception('hop')
Exception: hop

If I use setattr(m, 'append', r) then m.append(2) will fail. So is __iadd__ called on the class object ?

Also can I use settattr from class scope ? A naive attempt as in:

def r(*args, **kwargs): raise Exception('hop')
class Li(list):
    for s in {'__iadd__','append'}: setattr(Li, s, r)

fails with NameError: name 'Li' is not defined

Justin O Barber
  • 11,291
  • 2
  • 40
  • 45
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
  • Could you clarify what you're actually asking? If you want to patch built-ins, you'll need something like https://github.com/clarete/forbiddenfruit – jonrsharpe Apr 24 '15 at 22:29
  • Special method names are [looked up](https://docs.python.org/2/reference/datamodel.html#special-method-lookup-for-new-style-classes) on the type, not the instance dictionary. That's why it wors with `append` but not with `__iadd__`. – mata Apr 24 '15 at 22:59
  • @mata: aha - could you elaborate on an answer (with as many links as you like) - also (and this is the hard part) how can I do this from class scope ? That is dynamically call `setattr` once and for all on class initialization ? Forget about the use case - I am asking if it is possible – Mr_and_Mrs_D Apr 24 '15 at 23:01
  • @jonrsharpe: Why `setattr(m, 'append', r)` raises while `setattr(m, '__iadd__', r)` no ? And how can I use `for s in {'__iadd__','append'}: setattr(Li, s, r)` from class scope (inside Li) ? As it is fails with _NameError: name 'Li' is not defined_ – Mr_and_Mrs_D Apr 24 '15 at 23:05

1 Answers1

2

In new style classes, the instance __getattr__ method no longer intercepts calls made by built-in operations. When the built-in operation is used, the search begins at the class level instead of at the instance level. Nevertheless, an explicit call to the method name will work:

>>> class Li(list): pass
>>> m, n= Li([1]), Li([2])
>>> def r(*args, **kwargs):
        raise Exception('hop')

>>> setattr(m, '__iadd__', r)
>>> m += n           # no exception raised

>>> m.__iadd__(n)    # explicitly calling the method searches the instance first
Traceback (most recent call last):
  File "<pyshell#76>", line 1, in <module>
    m.__iadd__(n)
  File "<pyshell#73>", line 1, in r
    def r(*args, **kwargs): raise Exception('hop')
Exception: hop

One way to accomplish your second goal is to use a metaclass, especially if you only want to create the attributes once (as per your comments):

>>> class Meta(type):
    def __new__(meta, name, bases, attrdict):
        print 'adding class attributes!'
        for s in {'__iadd__','append'}:
            attrdict[s] = r
        return super(Meta, meta).__new__(meta, name, bases, attrdict)
    def __init__(cls, name, bases, attrdict):
        super(Meta, cls).__init__(name, bases, attrdict)


>>> class Li(list):
    __metaclass__ = Meta


adding class attributes!          # attributes added here
>>> t = Li()                      # but not on instance creation
>>> Li.__dict__
dict_proxy({'__module__': '__main__', '__metaclass__': <class '__main__.Meta'>, '__iadd__': <function r at 0x02AAA270>, '__dict__': <attribute '__dict__' of 'Li' objects>, '__weakref__': <attribute '__weakref__' of 'Li' objects>, '__doc__': None, 'append': <function r at 0x02AAA270>})
Justin O Barber
  • 11,291
  • 2
  • 40
  • 45
  • Thanks - re: the second part - I want to do it _once_, statically - I already do it for every instance in `__init__` but it's smelly (on _every_ instance creation). Disclaimer: curiosity :) – Mr_and_Mrs_D Apr 25 '15 at 04:36
  • Won't `for s in {'__iadd__','append'}: setattr(cls, s, r)` run on evey instance creation (`Li()`)? – Mr_and_Mrs_D Apr 25 '15 at 04:45
  • Ah. Wait. I see, yes. One moment. – Justin O Barber Apr 25 '15 at 04:46
  • Take your timezzzz (you'll need some trust me :D) – Mr_and_Mrs_D Apr 25 '15 at 04:57
  • Accepting although a metaclass is an overkill - I hoped there were something simpler but I could not find anything. Anyway this is a transitional refactoring - so I will go with calling seattr in "module scope" (see: http://stackoverflow.com/questions/28734919/access-base-class-attribute-in-derived-class-in-class-scope and linked: http://stackoverflow.com/questions/1765677/python-nested-classes-scope) – Mr_and_Mrs_D Apr 25 '15 at 14:18