How to clone a class that uses super()? - python

The following Python program:
def clone(cls):
return type(cls)("Clone" + cls.__name__, cls.__bases__, dict(vars(cls)))
class A:
def __init__(self):
super().__init__()
CloneA = clone(A)
CloneA()
raises the following error (CPython 3.8.4 interpreter):
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: super(type, obj): obj must be an instance or subtype of type
How can I clone (not subclass) A and avoid this error?

Simply add a __class__ = cls attribute:
def clone(cls):
namespace = dict(vars(cls), __class__=cls)
return type(cls)("Clone" + cls.__name__, cls.__bases__, namespace)
CloneA() calls CloneA.__call__(CloneA) resolved to type.__call__(CloneA), which calls CloneA.__new__(CloneA) resolved to type.__new__(CloneA) to create an instance self of CloneA, followed by CloneA.__init__(self), which calls A.__init__(self). Finally the latter calls super() (short for super(__class__, self)) which checks that self is an instance or subclass of __class__ that is of A, that is that self.__class__ or type(self) is a subclass of A. type(self) always returns CloneA while self.__class__ can differ, as Guido mentioned in PEP 3119:
Also, isinstance(x, B) is equivalent to issubclass(x.__class__, B) or issubclass(type(x), B). (It is possible type(x) and x.__class__ are not the same object, e.g. when x is a proxy object.)

Related

python linting and subclass properties

I have a superclass that uses some properties from the child class.
but unless I define properties in my superclass ALSO then the linter throws errors.
what's a pythonic way to get around this?
# parent
class DigItem:
def fetch_bq(self):
query = f'''select * from {self.table_id}'''
# subclass
class ChatLog(DigItem):
def __init__(self, set_name):
super().__init__(set_name)
self.table_id = biglib.make_table_id('chat_logs')
The above code errors with:
Instance of 'DigItem' has no 'table_id' memberpylint(no-member)
now, I can add the property to the superclass but that's pretty redundant and also risks overwriting the subclass
class DigItem:
def __init__(self, set_name):
self.table_id = None # set by child
This is down to the linter not being able to know AOT that this is a 'superclass' so it's fair enough as an error in a standalone instance.
But I'd prefer clean linting, pythonic code and not writing special hacky stuff just to shut up the linter.
In your example, DigItem has no __init__ at all (so it will be object's), so passing an argument to super().__init__() will fail
>>> class A: pass
...
>>> class B(A):
... def __init__(self):
... super().__init__("something")
...
>>> B()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
Further, you should (must) create the missing property in your parent in order for it to meaningfully make use of it in a method (otherwise different inheriting classes will not be able to make use of the method)
>>> class A:
... def foo(self):
... return self.bar
...
>>> class B(A):
... def __init__(self):
... self.bar = "baz"
...
>>> class C(A): pass # NOTE .bar is never defined!
...
>>> C().foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in foo
AttributeError: 'C' object has no attribute 'bar'
If the base class is not intended to be directly instantiable, consider making it an Abstract Base Class

How can I tell if a class has a method `__call__`?

A very simple class isn't a callable type:
>>> class myc:
... pass
...
>>> c=myc()
>>> callable(c)
False
How can I tell if a class has a method __call__? Why do the following two ways give opposite results?
>>> myc.__call__
<method-wrapper '__call__' of type object at 0x1104b18>
>>> __call__ in myc.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '__call__' is not defined
Thanks.
myc.__call__ is giving you the __call__ method used to call myc itself, not instances of myc. It's the method invoked when you do
new_myc_instance = myc()
not when you do
new_myc_instance()
__call__ in myc.__dict__ gives you a NameError because you forgot to put quotation marks around __call__, and if you'd remembered to put quotation marks, it would have given you False because myc.__call__ isn't found through myc.__dict__. It's found through type(myc).__dict__ (a.k.a. type.__dict__, because type(myc) is type).
To check if myc has a __call__ implementation for its instances, you could perform a manual MRO search, but collections.abc.Callable already implements that for you:
issubclass(myc, collections.abc.Callable)
The reason why myc.__call__ gives you something back 1 instead of raising an AttributeError is because the metaclass of myc (type) has a __call__ method. And if an attribute isn't found on the instance (in this case your class) it's looked up on the class (in this case the metaclass).
For example if you had a custom metaclass the __call__ lookup would've returned something different:
class mym(type):
def __call__(self, *args, **kwargs):
return super().__call__(*args, **kwargs)
class myc(metaclass=mym):
pass
>>> myc.__call__
<bound method mym.__call__ of <class '__main__.myc'>>
Regarding the __call__ in myc.__dict__ that has been answered in the comments and the other answer already: You just forgot the quotations:
>>> '__call__' in myc.__dict__
False
However it could also be that myc subclasses a callable class, in that case it would also give False:
class mysc:
def __call__(self):
return 10
class myc(mysc):
pass
>>> '__call__' in myc.__dict__
False
So you need something more robust. Like searching all the superclasses:
>>> any('__call__' in cls.__dict__ for cls in myc.__mro__)
True
Or as pointed out by user2357112 use collections.Callable which overrides the issubclass-check so that it looks for __call__:
>>> from collections.abc import Callable
>>> issubclass(myc, Callable)
True
1 That's also the reason why you can't just use hasattr(myc, '__call__') to find out if it has a __call__ method itself.

When does copy(foo) call foo.copy?

I'm designing a base class, and I want it to define a base behaviour for copy.copy.
This behaviour consists in printing a warning in the console, and then copying the instance as if it had no __copy__ attribute.
When one defines a blank Foo class and copies an instance of it, the copy function returns a new instance of that class, as shown by the following session:
>>> class Foo: pass
...
>>> foo = Foo()
>>> foo2 = copy(foo)
>>> foo is foo2
False
Now if the Foo class defines a __copy__ instance method, the latter will be called when trying to pass an instance to copy:
>>> class Foo:
... def __copy__(self):
... print("Copying")
...
>>> foo = Foo()
>>> copy(foo)
Copying
So as I understand it, the flow of execution of the copy function would be:
Try to access the object's __copy__ attribute
If present, call it
If absent, perform a generic copy
But now, I want to capture the copy function's accessing the __copy__ attribute, through defining a __getattr__ method, and then simulate the absence of this attribute, by raising an AttributeError:
>>> class Foo:
... def __getattr__(self, attr):
... if attr == '__copy__':
... print("Accessing '__copy__'")
... raise AttributeError
...
Then the __copy__ attribute does not seem to be accessed anymore:
>>> foo = Foo()
# Actual behaviour of copy(foo)
>>> copy(foo)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\ProgramData\Miniconda3\lib\copy.py", line 96, in copy
rv = reductor(4)
TypeError: 'NoneType' object is not callable
# Expected behaviour of copy(foo)
>>> foo.__copy__()
Accessing '__copy__'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __getattr__
AttributeError
What am I missing in the execution flow of the copy function, with regards to the __copy__ attribute?
As far as I understand, given a foo object with no attribute bar, it should behave exactly the same, whether it has a __getattr__ method that fails on bar, or it doesn't define anything.
Is this statement exact?
Short answer seems to be "no".
Within the copy.copy method we find this (summarized)
cls = type(x)
...
copier = getattr(cls, "__copy__", None)
We see that the function uses getattr on the class, not the instance.
And there's no way to have __getattr__ called when getattr is called on the class (just because it's an instance method)
Demo (sorry, I would have prefered a working demo)
class Foo:
def __getattr__(self,attr):
if attr == '__copy__':
return "WORKED"
foo = Foo()
print(getattr(foo, "__copy__",None))
print(getattr(Foo, "__copy__",None))
this returns:
WORKED
None
So it's not possible to fool the original copy.copy module into believing there's a __copy__ attribute without creating a __copy__ method.

How to make an Python subclass uncallable

How do you "disable" the __call__ method on a subclass so the following would be true:
class Parent(object):
def __call__(self):
return
class Child(Parent):
def __init__(self):
super(Child, self).__init__()
object.__setattr__(self, '__call__', None)
>>> c = Child()
>>> callable(c)
False
This and other ways of trying to set __call__ to some non-callable value still result in the child appearing as callable.
You can't. As jonrsharpe points out, there's no way to make Child appear to not have the attribute, and that's what callable(Child()) relies on to produce its answer. Even making it a descriptor that raises AttributeError won't work, per this bug report: https://bugs.python.org/issue23990 . A python 2 example:
>>> class Parent(object):
... def __call__(self): pass
...
>>> class Child(Parent):
... __call__ = property()
...
>>> c = Child()
>>> c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: unreadable attribute
>>> c.__call__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: unreadable attribute
>>> callable(c)
True
This is because callable(...) doesn't act out the descriptor protocol. Actually calling the object, or accessing a __call__ attribute, involves retrieving the method even if it's behind a property, through the normal descriptor protocol. But callable(...) doesn't bother going that far, if it finds anything at all it is satisfied, and every subclass of Parent will have something for __call__ -- either an attribute in a subclass, or the definition from Parent.
So while you can make actually calling the instance fail with any exception you want, you can't ever make callable(some_instance_of_parent) return False.
It's a bad idea to change the public interface of the class so radically from the parent to the base.
As pointed out elsewhere, you cant uninherit __call__. If you really need to mix in callable and non callable classes you should use another test (adding a class attribute) or simply making it safe to call the variants with no functionality.
To do the latter, You could override the __call__ to raise NotImplemented (or better, a custom exception of your own) if for some reason you wanted to mix a non-callable class in with the callable variants:
class Parent(object):
def __call__(self):
print "called"
class Child (Parent):
def __call__(self):
raise NotACallableInstanceException()
for child_or_parent in list_of_children_and_parents():
try:
child_or_parent()
except NotACallableInstanceException:
pass
Or, just override call with pass:
class Parent(object):
def __call__(self):
print "called"
class Child (Parent):
def __call__(self):
pass
Which will still be callable but just be a nullop.

Python: Why can't I use `super` on a class?

Why can't I use super to get a method of a class's superclass?
Example:
Python 3.1.3
>>> class A(object):
... def my_method(self): pass
>>> class B(A):
... def my_method(self): pass
>>> super(B).my_method
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
super(B).my_method
AttributeError: 'super' object has no attribute 'my_method'
(Of course this is a trivial case where I could just do A.my_method, but I needed this for a case of diamond-inheritance.)
According to super's documentation, it seems like what I want should be possible. This is super's documentation: (Emphasis mine)
super() -> same as super(__class__,
<first argument>)
super(type) -> unbound super object
super(type, obj) -> bound super
object; requires isinstance(obj, type)
super(type, type2) -> bound super
object; requires issubclass(type2,
type)
[non-relevant examples redacted]
It looks as though you need an instance of B to pass in as the second argument.
http://www.artima.com/weblogs/viewpost.jsp?thread=236275
According to this it seems like I just need to call super(B, B).my_method:
>>> super(B, B).my_method
<function my_method at 0x00D51738>
>>> super(B, B).my_method is A.my_method
True

Categories