This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: hackcheck is broken in association with __setattr__
Type: behavior Stage: resolved
Components: Interpreter Core, Library (Lib) Versions: Python 3.2, Python 3.3, Python 3.4, Python 2.7
process
Status: closed Resolution: out of date
Dependencies: Superseder:
Assigned To: Nosy List: devkid, eryksun, iritkatriel
Priority: normal Keywords:

Created on 2015-01-19 22:22 by devkid, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (6)
msg234329 - (view) Author: Alfred Krohmer (devkid) Date: 2015-01-19 22:22
The following code:


import traceback
import sys

from PyQt5.QtCore import Qt

class MetaA(type):
    pass

class A(metaclass=MetaA):
    pass

class MetaB(type):
    pass

class B(metaclass=MetaB):
    pass

for ClassB in B, Qt:

    print("Trying class %s" % (ClassB.__name__, ))

    class MyMeta(type(A), type(ClassB)):
        def __setattr__(cls, key, value):
            print(cls)
            super(type, cls).__setattr__(key, value)

    class MyClass(A, ClassB, metaclass=MyMeta):
        pass

    try:
        setattr(MyClass, 'abc', 123)
    except:
        traceback.print_exc(file=sys.stdout)

    try:
        type.__setattr__(MyClass, 'test', 42)
    except:
        traceback.print_exc(file=sys.stdout)


Fails with the following output:


Trying class B
<class '__main__.MyClass'>
Traceback (most recent call last):
  File "test3.py", line 31, in <module>
    setattr(MyClass, 'abc', 123)
  File "test3.py", line 25, in __setattr__
    super(type, cls).__setattr__(key, value)
TypeError: can't apply this __setattr__ to type object
Trying class Qt
<class '__main__.MyClass'>
Traceback (most recent call last):
  File "test3.py", line 31, in <module>
    setattr(MyClass, 'abc', 123)
  File "test3.py", line 25, in __setattr__
    super(type, cls).__setattr__(key, value)
TypeError: can't apply this __setattr__ to sip.wrappertype object
Traceback (most recent call last):
  File "test3.py", line 36, in <module>
    type.__setattr__(MyClass, 'test', 42)
TypeError: can't apply this __setattr__ to sip.wrappertype object


The metaclass of a class should be able to update its class' __dict__ my calling super(type, cls).__setattr__ (there is no other way known to me to do this). Furthermore, if subclassing an external class, like Qt, it should be possible to use type.__setattr__(MyClass, ...) externally to change the class' attributes.

The error is caused by the hackcheck function in objects/typeobject.c.
msg234333 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2015-01-20 01:08
>            super(type, cls).__setattr__(key, value)

In your case, super(type, cls).__setattr__ references object.__setattr__.

    >>> super(type, MyClass).__setattr__.__objclass__
    <class 'object'>

That's from the method resolution order (__mro__):

    >>> print(*MyMeta.__mro__, sep='\n')
    <class '__main__.MyMeta'>
    <class '__main__.MetaA'>
    <class '__main__.MetaB'>
    <class 'type'>
    <class 'object'>

Instead use super(MyMeta, cls), or in Python 3 just use super() in a method (under the hood the function uses a closure variable named __class__).

    >>> super(MyMeta, MyClass).__setattr__.__objclass__
    <class 'type'>

>        type.__setattr__(MyClass, 'test', 42)

The above won't work for a Qt subclass. You need __setattr__ from sip.wrappertype.

    >>> type.__setattr__(QtClass, 'test', 42)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can't apply this __setattr__ to sip.wrappertype object

    >>> print(*QtMeta.__mro__, sep='\n')
    <class '__main__.QtMeta'>
    <class '__main__.MetaA'>
    <class 'sip.wrappertype'>
    <class 'type'>
    <class 'object'>

    >>> super(QtMeta, QtClass).__setattr__.__objclass__
    <class 'sip.wrappertype'>
    >>> super(QtMeta, QtClass).__setattr__('test', 42)
    >>> QtClass.test
    42
msg234359 - (view) Author: Alfred Krohmer (devkid) Date: 2015-01-20 08:40
Can you elaborate what QtClass and QtMeta is in your case?

My original example was reduced to a minimal case and seems to work with your suggestions.

The complete example involving SQLalchemy is here:

http://stackoverflow.com/questions/28032928/sqlalchemy-multiple-base-classes-not-working

and does, however, not work.

If I try to do

    # ...

    def __setattr__(cls, key, value):
        super(type(QMediaPlaylist), cls).__setattr__(cls, key, value)
        return

The program segfaults when instantiating the Playlist class. However, this approach seems a little bit strange to me anyhow.

The same happens when I try to do:

    # ...

    def __setattr__(cls, key, value):
        super(type(base), cls).__setattr__(cls, key, value)
        return

I think that comes from PyQt specific attributes SQLalchemy is trying to set / replace.

So, coming back to the original question, how can I actually set an attribute of my class Playlist from within its metaclass without involving the parent classes of the subclass (type(base) and type(QMediaPlaylist))? Because the __setattr__ from PyQt won't work (segfault) and the one from SQLalchemy does stupid stuff.
msg234369 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2015-01-20 11:24
>    def __setattr__(cls, key, value):
>        super(type(QMediaPlaylist), cls).__setattr__(cls, key, value)
>        return
>
> The program segfaults when instantiating the Playlist class.

I'd expect a TypeError because of the extra cls argument. It's already a bound method. 

FYI, the above finds the next metaclass after type(QMediaPlaylist) in PlaylistMeta.__mro__. It happens that type(QMediaPlaylist) inherits __setattr__ from the next in line (sip.wrappertype), so by a stroke of luck it 'works' (not really since this skips the incompatible sqlalchemy __setattr__).

Consider making a playlist class that *has* a SQL table, not one that *is* a SQL table, i.e. use composition instead of inheritance. That sidesteps the incompatible metaclasses.
msg234390 - (view) Author: Alfred Krohmer (devkid) Date: 2015-01-20 20:26
> I'd expect a TypeError because of the extra cls argument. It's already a bound method.

Sorry, that was a typo.

> Consider making a playlist class that *has* a SQL table, not one that *is* a SQL table, i.e. use composition instead of inheritance. That sidesteps the incompatible metaclasses.

That would be indeed a solution, but not for the original problem.

I think I have an example now that makes my point clear.

The following code works as it should:

import traceback
import sys

class MyMeta(type):
    def __setattr__(cls, key, value):
        print("OK")

class MyClass(metaclass=MyMeta):
    pass

MyClass.abc = 12 # outputs "OK"
try:
    print(MyClass.abc)
except:
    traceback.print_exc(file=sys.stdout) # exception comes here as expected

type.__setattr__(MyClass, 'test', 42) # outputs nothing
print(MyClass.test) # outputs "42"

If I get this right, this should be **valid code** (and it should **not** be a bug, that this actually works).

However, above define MyMeta like following:

from PyQt5.QtMultimedia import QMediaPlaylist

class MyMeta(type(QMediaPlaylist)):
    def __setattr__(cls, key, value):
        print("OK")

And you get:

TypeError: can't apply this __setattr__ to PyQt5.QtCore.pyqtWrapperType object

I think that this actually **is** unexpected behaviour. I'm **not** trying to apply __setattr__ to PyQt5.QtCore.pyqtWrapperType but to MyClass!
msg380561 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2020-11-08 18:59
I tried to look up what pyqtWrapperType is and found that it has been removed from QtCore.

Is this issue still relevant?
History
Date User Action Args
2022-04-11 14:58:12adminsetgithub: 67465
2020-11-30 19:31:04iritkatrielsetstatus: pending -> closed
resolution: out of date
stage: resolved
2020-11-08 18:59:46iritkatrielsetstatus: open -> pending
nosy: + iritkatriel
messages: + msg380561

2015-01-20 20:26:45devkidsetmessages: + msg234390
2015-01-20 11:24:49eryksunsetmessages: + msg234369
2015-01-20 08:40:20devkidsetmessages: + msg234359
2015-01-20 01:08:38eryksunsetnosy: + eryksun
messages: + msg234333
2015-01-19 22:22:19devkidcreate