In Python 3, how can I call an inherited method in another class method?
class A:
name = 'foo'
def get_name(self):
return self.name
class B(A):
#classmethod
def do_other(cls):
cls.get_name()
in the cls.get_name() it complains that 'Parameter "self" unfilled'.
How can I overcome this without having to change the do_other to a regular method?
You can accomplish this by creating a temporary instance.
This is one way.
class A:
name = 'foo'
def __init__(self):
self.name = A.name
def get_name(self):
return self.name
class B(A):
#classmethod
def do_other(cls):
return B.get_name(cls)
print(B.do_other())
This is the second way
class B(A):
#classmethod
def do_other(cls):
return A().get_name()
Here you can also replace A() with B() since class B is inheriting from class A
You acutally only have to return the cls.get_name(cls)
class A:
name = 'foo'
def get_name(self):
return self.name
class B(A):
#classmethod
def do_other(cls):
return cls.get_name(cls)
print(B.do_other())
Related
How to inherit all class 'A' attributes and methods, but 'b()'?
class A:
def __init__(self):
# attributes
pass
#classmethod
def b(cls):
# logic
pass
class B(A):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def b(self):
# nothing
pass
do not use this old method( if there is another way to do it ):
class B(A):
def __init__(self, attributes):
super().__init__(self, attributes)
You can reimplement b() to raise an error:
class A:
def __init__(self):
# attributes
pass
#classmethod
def b(cls):
# logic
pass
class B(A):
def __init__(self, **kwargs):
super().__init__(**kwargs)
#classmethod
def b(cls):
raise TypeError("method b is not supported in class B")
Also, if b() is a classmethod, you should probably override it as a classmethod.
Put that method in a separate class and don't inherit it.
class A:
def __init__(self):
# attributes
pass
class A1:
#classmethod
def b(cls):
# logic
pass
class B(A):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Use multiple inheritance when you want that method.
class C(A,A1):
pass
I'm trying to implement an abstract class with attributes and I can't get how to define it simply.
I just want to define the attribute name to constrain child classes to have it but I don't want to copy/paste getters & setters in every classes that inherit my abstract class.
Here are solutions I found but not very elegant in my opinion:
Maybe the most efficient and robust way, but quite ugly and redundant. We have to put 'def a(): pass', in each child class
class AbstractC(ABC):
#property
#abstractmethod
def a(self):
pass
class ConcreteC1(AbstractC):
def __init__(self, name):
self.a = name
def a(self):
pass
class ConcreteC2(AbstractC):
def __init__(self, name):
self.a = name
class ConcreteC3(AbstractC):
def __init__(self, name):
self.poney = name
ConcreteC1('foobar') # ok
ConcreteC2('foobar') # error !
ConcreteC3('foobar') # error !
Quite the same, but uglier imo
class AbstractC(ABC):
#property
#abstractmethod
def a(self):
pass
class ConcreteC1(AbstractC):
a = None
def __init__(self, name):
self.a = name
class ConcreteC2(AbstractC):
def __init__(self, name):
self.a = name
class ConcreteC3(AbstractC):
def __init__(self, name):
self.poney = name
ConcreteC1('foobar') # ok
ConcreteC2('foobar') # error !
ConcreteC3('foobar') # error !
Most compact way, but not robust. No error if 'a' is missing
class AbstractC(ABC):
#abstractmethod
def __init__(self, val):
self.a = val
class ConcreteC1(AbstractC):
def __init__(self, name):
self.a = name
class ConcreteC2(AbstractC):
def __init__(self, name):
self.poney = name
ConcreteC1('foobar') # ok
ConcreteC2('foobar') # no error !
So is there a way to get an elegant, robust and compact abstract class with abstract attribute ? Or am I trying to get something impossible ? I was thinking about something close to that :
class AbstractC(ABC):
#property
#abstractmethod
def a(self):
pass
class ConcreteC(AbstractC):
def __init__(self, name):
self.a = name
If there is no such solution, what is the best one ?
You could misuse namedtuples for fancy inheritance
from collections import namedtuple
BaseAttributes = namedtuple('base', ['attr1', 'attr2'])
print(BaseAttributes('one', 2))
class SomethingElse(BaseAttributes):
def method(self):
return 3
blubb = SomethingElse('A', 5)
blubb.method()
but imho your last proposal(s) makes sense if you raise NotImplementedError, e.g.:
class AbstractC(ABC):
def a(self):
raise NotImplementedError('Implement _a_ method')
class ConcreteC(AbstractC):
def __init__(self, name, *args, **kwargs):
super().__init__(*args, **kwargs)
self.a = name
Maybe this will help. I made a class which inherits from ABC. It defines the method __init_subclass__ that is invoked after a new subclass is created. It does the next: For each abstract property declared, search the same method in the subclass. If it exists (its a function object) convert it to a property and replace it in the subclass dictionary.
from abc import ABC, abstractmethod
class Foo(ABC):
def __init_subclass__(cls):
super().__init_subclass__()
###### This is the new part. I explain it at the end of the answer
for name, value in attrs.items():
if name not in cls.__dict__:
setattr(cls, name, property(lambda *args, **kwargs: value))
######
# Iterate throught all abstract methods on the class
for name in Foo.__abstractmethods__:
absmethod = Foo.__dict__[name]
# Check if the abstract method is a property
if not isinstance(absmethod, property):
continue
# Check if there is a method defined in the subclass with the same name
if name not in cls.__dict__ or not callable(cls.__dict__[name]):
continue
method = cls.__dict__[name]
# If the method is not already a property, we decorate it automatically...
if not isinstance(method, property):
setattr(cls, name, property(method))
#property
#abstractmethod
def a(self):
return 1
Now define a subclass and test it:
class Bar(Foo):
def __init__(self):
pass
def a(self):
return 2
#property
def b(self):
return 3
obj = Bar()
print(obj.a)
print(obj.b)
Output will be:
2
3
The next code will raise an error, because not all abstract methods are implemented:
class Qux(Foo):
pass
EDIT:
Now you can also do:
class Bar(Foo, a=1):
pass
print(Bar().a) # 1
There's still a problem. If i choose the implementation that raise an error, i have to add #property to the method or i can call ConcreteC().a even if a is not set and it will not raise the error:
class AbstractC(ABC):
def a(self):
raise NotImplementedError('Implement _a_ method')
class ConcreteC(AbstractC):
def __init__(self, val):
super().__init__()
self.poney = val
In [3]: ConcreteC('foobar').a
Out[3]: <bound method AbstractC.a of <__main__.ConcreteC object at 0x7f2e1c6b0518>>
But if i add #property i get an error :
class AbstractC(ABC):
#property
def a(self):
raise NotImplementedError('Implement _a_ method')
class ConcreteC(AbstractC):
def __init__(self, val):
super().__init__()
self.a = val
In [4]: ConcreteC('foobar')
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-55-587237cb76e5> in <module>
----> 1 ConcreteC('foobar')
~/workspace/draft.py in __init__(self, val)
151 def __init__(self, val):
152 super().__init__()
--> 153 self.a = val
154
155
AttributeError: can't set attribute
EDIT:
Here the solution I chose:
class AbstractC(ABC):
#property
def a(self):
try:
return self._a
except AttributeError:
raise NotImplementedError('Implement _a_ method')
#a.setter
def a(self, val):
self._a = val
class ConcreteC(AbstractC):
def __init__(self, val):
self.a = val
This way I can edit 'a' very simply and if it's not definied, an exception is raised on get. I didn't know that to make a setter work, it must has the same name as the property.
In the end, what I wanted isn't an abstract attribute, but a concrete one in an abstract class.
In [1]: c = ConcreteC('foobar')
In [2]: c.a
Out[2]: 'foobar'
In [3]: c.a = 'poney'
In [4]: c.a
Out[4]: 'poney'
class A(object):
__A = None
def get_a(self):
return self.__A
def set_a(self, value):
self.__A = value
class B(A):
def method_b(self, value):
self.set_a(value)
class C(A):
def method_c(self)
self.get_a()
Someone can to explain me how can i to catch installed value in method_b inside my 'C' class method?
P.S. In this variant i just getting nothing.
Python isn't Java; you don't need setters & getters here: just access the attributes directly.
There are three problems with your code.
C.method_c() has no return statement, so it returns None.
You are using __ name mangling when that's exactly what you don't want.
In A.set_a() you want to set a class attribute, but your assignment instead creates an instance attribute which shadows the class attribute.
Here's a repaired version.
class A(object):
_A = 'nothing'
def get_a(self):
return self._A
def set_a(self, value):
A._A = value
class B(A):
def method_b(self, value):
self.set_a(value)
class C(A):
def method_c(self):
return self.get_a()
b = B()
c = C()
print(c.method_c())
b.method_b(13)
print(c.method_c())
output
nothing
13
Here's a slightly more Pythonic version:
class A(object):
_A = 'nothing'
class B(A):
def method_b(self, value):
A._A = value
class C(A):
pass
b = B()
c = C()
print(c._A)
b.method_b(13)
print(c._A)
How to save code duplication in the following scenario ?
say Aand B are two classes having a common function(say) name
class A(object):
name = 'foo'
#property
def name(self): # the common function
return self.name
similarly B
class B(object):
name = 'bar'
#property
def name(self):
return self.name
One way would be to make a class from which both of them inherit from, and define name there.
Any good alternatives ?
If you're really determined to avoid inheritance, just define a function outside of either class:
def get_name(object):
return object.name
class A(object):
name = 'foo'
def get_name(self): # the common function
return self.name
class B(A):
pass
In this case B would inherit from A
Is there a reason you can't have B inherit from A?
class B(A):
name = 'bar'
Since you are decorating name with #property, I am assuming you want this to be an instance variable. If you want this to return a more private variable, let's call it _name, you have to do:
class A(object):
def __init__(self):
self._name = 'foo'
#property
def name(self):
return self._name
You can't have both a variable and a function have the same name, since the latter will simply override the former. If you want a base class that takes care of this, it would look like this:
class HasName(object):
def __init__(self, name):
self._name = name
#property
def name(self):
return self._name
class A(HasName):
def __init__(self):
self._name = 'foo'
class B(HasName):
def __init__(self):
self._name = 'bar'
You can also call the constructor in HasName.
Assuming self.name stands in for a more complex method, the easiest way to cut down on duplicated code is to move the function out to the module and have it take an arbitrary object as a parameter. Then, if you still want to tie the method directly to the class, you can add a short method that dispatches to the module function.
def _name(obj):
return obj.name
class A(object):
# ...
#property
def name(self):
return _name(self)
class B(object):
# ...
#property
def name(self):
return _name(self)
Note that this will not work well if A.name and B.name have completely different behaviors. If the _name function starts checking the type of the object given, reconsider whether you really want to abstract that functionality in the first place.
Im a bit confused about inherited instance variables in ABCs. I have written an example to show my confusion. Class A needs a list which class B inherits but it must be an instance object rather than a class object. However class B also needs its own instance variable local. Can anyone set me straight?
#!python
from abc import ABCMeta, abstractmethod, abstractproperty
import unittest
class A(object):
__metaclass__ = ABCMeta
_internal = ['initialized']
#property
def internal(self):
return self._internal
def get_a(self):
return self._internal
#abstractmethod
def set_a(self, value):
pass
class B(A):
def __init__(self):
self.local = 'OK'
def get_local(self):
return self.local
def set_a(self, value):
self._internal.append(value)
class TestCase(unittest.TestCase):
def test_implementation(self):
self.assertEqual(['initialized'], B().get_a() ) # this passes but for wrong reason
b_used = B().set_a('used')
b_unused = B()
print "b_used.get_a() should return ['initialized','used']"
print "b_unused.get_a() should return ['initialized']"
print "b_used.get_local() should equal b_unused.get_local() = 'OK'"
self.assertEqual(['initialized'], b_unused.get_a()) # >> fails with ['initialized'] =! ['initialized', 'used']
self.assertNotEqual(b_unused.get_a(), b_used.get_a())
if __name__ == "__main__":
unittest.main()
The problem is that _internal is a class obj of class A. I need it to be an instance object of class B.
Thanks In advance
You should initialize instance attributes in __init__() and call the base class __init__() in B:
class A(object):
__metaclass__ = ABCMeta
def __init__(self):
self._internal = ['initialized']
...
class B(A):
def __init__(self):
A.__init__(self)
self.local = 'OK'
...
You should also fix your unit test:
class TestCase(unittest.TestCase):
def test_implementation(self):
self.assertEqual(['initialized'], B().get_a() ) # this passes but for wrong reason
b_used = B()
b_used.set_a('used')
b_unused = B()
...
Instance attributes should be defined in a method, eg __init__, by setting them on self.