Reusing method from another class without inheritance or delegation in Python - python

I want to use a method from another class.
Neither inheritance nor delegation is a good choice (to my understanding) because the existing class is too complicated to override and too expensive to instanciate.
Note that modifying the existing class is not allowed (legacy project, you know).
I came up with a way:
class Old:
def a(self):
print('Old.a')
class Mine:
b = Old.a
and it shows
>>> Mine().b()
Old.a
>>> Mine().b
<bound method Old.a of <__main__.Mine object at 0x...>>
It seems fine.
And I tried with some more complicated cases including property modification (like self.foo = 'bar'), everything seems okay.
My question:
What is actually happening when I define methods like that?
Will that safely do the trick for my need mentioned above?

Explanation
What's happening is that you are defining a callable class property of class Mine called b. However, this works:
m = Mine()
m.b()
But this won't:
Mine.b()
Why doesn't the second way work?
When you call a function of a class, python expects the first argument to be the actual object upon which the function was called. When you do this, the self argument is automatically passed into the function behind the scenes. Since we called Mine.b() without an instantiated instance of any object, no self was passed into b().
Will this "do the trick"?
As for whether this will do the trick, that depends.
As long as Mine can behave the same way as Old, python won't complain. This is because the python interpreter does not care about the "type" of self. As long as it walks like a duck and quacks like a duck, it's a duck (see duck typing). However, can you guarantee this? What if someone goes and changes the implementation of Old.a. Most of the time, as a client of another system we have no say when the private implementation of functions change.
A simpler solution might be to pull out the functionality you are missing into a separate module. Yes, there is some code duplication but at least you can be confident the code won't change from under you.
Ultimately, if you can guarantee the behavior of Old and Mine will be similar enough for the purposes of Old.a, python really shouldn't care.

Related

How are dunder methods accessed?

number = 5
print(number.__class__)
print(number.__str__())
print(number.__add__(1))
#Output
<class 'int'>
5
6
I guess I'm trying to see if I understand this correctly.
So there's different ways to access the dunder methods. In the first case, it looks as though I'm accessing the method as though it were an attribute of the class int. In this case, is __class__ using an #property decorator to access it as though it were an attribute? If that's the case, then it makes sense.
Second and third make sense. __str___ is a dunder method defined in the class int that takes no arguments. You call it like a method. __add__ takes a argument, which is the number you're adding to it. So you have to call it like a method with an argument. So none of these use #property decorators.
I guess what makes it more confusing is that technically the __str__ can be made into an attribute with the #property decorator. I tested it out myself on my own class and it definitely works either way, so it seems a bit arbitrary which ones are accessed like attributes and which ones are accessed like methods. Assuming my theory is correct.
So there's different ways to access the dunder methods. In the first case, it looks as though I'm accessing the method as though it were an attribute of the class int.
That's because __class__ is an attribute, not a method.
Another distinction to make is that the way you access methods is always the same. Dunder methods are not magical in this. You use a . between the instance and the method name. Whether you are referring just to the method or calling it to get its result is another matter.
While this is a reasonable exercise to understand how Python works, remember that you should not usually call dunder methods directly. Python offers other syntax that will call these methods under the hood. For example, use str(number) instead of number.__str__() and a + b rather than a.__add__(b).
The main reason for dunder methods is that they allow you to override default behavior in your own classes. They aren't intended to be called directly.

Is it possible to properly copy a class using type

According to this answer, a class object cls can be replicated with
cls_copy = type('cls_copy', cls.__bases__, dict(cls.__dict__))
This works perfectly for most normal cases. It does not work when the metaclass of cls is not type. My initial naive fix was to do
cls_copy = type(cls)('cls_copy', cls.__bases__, dict(cls.__dict__))
However, this is simply pointless. There is no way to know what a metaclass does, as this answer to a related question points out, how it transforms the input dictionary, what additional keywords it requires, etc.
The original use of type is almost good enough with a couple of minor exceptions:
The __dict__ created by metaclasses that do not end up calling type.__new__ may be of a different type than the usual proxy object.
Classes extending the copy will not have the correct metaclass, which may cause unexpected behavior.
Any properties or other data descriptors defined in the original metaclass will no longer be available on the class object.
I am willing to ignore item #1. It is a corner case that I am willing to document away should I find a viable solution to the other items. Items #2 and #3 can be solved if it were possible to change the metaclass of the copy. I tried (again, naively)
cls_copy = type('cls_copy', cls.__bases__, dict(cls.__dict__),
metaclass=type(cls))
This just raised a TypeError, as could be expected:
TypeError: __init_subclass__() takes no keyword arguments
This makes sense in light of the docs:
Like its identity, an object’s type is also unchangeable. 1
However, the footnote states that
It is possible in some cases to change an object’s type, under certain controlled conditions. It generally isn’t a good idea though, since it can lead to some very strange behaviour if it is handled incorrectly.
What are the conditions under which it is possible to change an object's type, specifically that of a class? Is this one of those cases, and if so, how?
Note
I am aware that copy.deepcopy and inheritance are viable alternatives here. For the purpose of this question, I wish to ignore those alternatives and stick with using type-related techniques.
You could use type.__new__(type(cls), cls.__name__, cls.__bases__, dict(cls.__dict__)). This uses the normal type creation process, but creates an instance of type(cls) instead of type.
As for the __metaclass__ issue, I think that is because __metaclass__ is usually what is called, so type can't use it.

What is dynamic dispatch and duck typing?

When using Pycharm, It often points out an error, saying:
Unresolved reference 'name'. This inspection detects names that should
resolve but don't. Due to dynamic dispatch and duck typing, this is
possible in a limited but useful number of cases. Top-level and
class-level items are supported better than instance items.
I've snooped around about this, but most questions and information I find is about preventing the message from being shown. what I want to know is:
What is dynamic dispatch/duck typing?
What are (or an example of) these "useful number of cases"?
Python uses a duck typing convention. This means that you do not have to specify what type a name is. Unlike in Java, for example, where you must specify explicitly that variable may be type int or Object. Essentially, type checking is done at runtime.
"If it walks like a duck and it quacks like a duck, then it must be a duck."
In Python everything will seem to work until you use try to manipulate an object in a way that it is not designed to. Basically, an object may not have a certain method or attribute that another might, and you won't find this out until Python throws an error upon trying it.
Dynamic Dispatch is the practice of the compiler or environment choosing which version of a polymorphic function to use at runtime. If you have multiple implementations of a method, you can use them in different ways despite the methods having the same or similar properties/attributes. Here's an example:
class Foo:
def flush():
pass
class Bar:
def flush():
pass
Both classes have a flush() method but the correct name is chosen at runtime.
Python is not the best example of this process since methods can take multiple parameters and don't have to be reimplemented. Java is a better example, but I'm not fluent enough in it to provide a correct example.
The warning means that you're using a variable that PyCharm doesn't recognise, but due to Python's dynamic nature it can't be sure if it's right or you're right.
For example you may have the following code:
class myClass():
def myfunc(self):
print(self.name)
PyCharm will probably complain that self.name can't be resolved. However, you may use the class like this:
my_class = myClass()
my_class.name = "Alastair"
my_class.myfunc()
which is perfectly valid (albeit brittle).
The message goes on to say that it's more confident about attribute and methods that are less ambiguous. For example:
class myClass():
my_instance_var = "Al"
def myfunc(self):
print(self.my_instance_var)
As my_instance_var is defined in the source code (a class attribute), PyCharm can be confident it exists.
(Don't use class attributes unless you know what you're doing!)

Is it possible to avoid writing 'dot' each time when calling class method?

Sorry for somewhat unclear question. I'm actually wondering whether it's possible in Python not to mention class name, when you call class's methods iteratively? I mean to write instead of:
SomeClass.Fun1()
SomeClass.Fun2()
...
SomeClass.Fun100()
Something like:
DoWith SomeClass:
Fun1()
Fun2()
...
Fun100()
?
There are several methods to achieve that (from SomeClass import *, locals().update(SomeClass.__dict__())), but what you're trying is not really logical:
In 90% of cases you're not calling static class methods, but member functions, which need a single instance to operate on. You do realize that the first, the self argument that you typically see on methods is important, because it gives you access to the instance's namespace. So even in methods, you use self.my_member instead of my_member. That's an important python concept, and you should not try to avoid it -- there's a difference between the local name space and the attributes of an instance.
What you can do, however, is having a short handle, without any overhead:
my_instance = SomeClass() #notice, this is an instance of SomeClass, not the class or type itself
__ = my_instance
that can save you a lot of typing. But I prefer clarity over saved typing (hell, vim has good autocompletion plugins for Python).
yes, just try from SomeClass import * (after moving SomeClass to an other file of course)

Call method from top class in hierarchy instead of override

Let's say I have classes Base(object) and Derived(Base). These both implement a function foo, with Derived.foo overriding the version in Base.
However, in one of the methods, say Base.learn_to_foo, I want to call Base.foo instead of the derived version regardless of whether it was overridden. So, I call Base.foo(self) in that method:
class Base(object):
# ...
def learn_to_foo(self, x):
y = Base.foo(self, x)
# check if we foo'd correctly, do interesting stuff
This approach seems to work and from a domain standpoint, it makes perfect sense, but somehow it smells a bit fishy. Is this the way to go, or should I refactor?
The answer is NOT to use the super() function. The way you are doing is exactly right as you don't want to invoke the virtual method that is overridden in the super class. Since you seem to want the base class' exact implementation all the time, the only way is to get the base class' unbound method object back, bound it to self, which could be an instance of Base or Derived. Invoke the unbound method with self supplied explicitly as the first parameter gives you back a bound method. From this point forward, Base.foo will be acting on the instance self's data. This is perfectly acceptable and is the way Python deals with non-virtual method invocation. This is one of the nice things that Python allows you to do that Java does not.
It is recommended:
def learn_to_foo(self, x):
super(Derived, self).foo(x)
More information at http://docs.python.org/library/functions.html#super
An alternative is to use the 'super' built-in:
super(Derived, self).foo(x) # Python 2
super().foo(x) # Python 3

Categories