Is `self` actually mandatory for class methods in Python? - python

I saw a code snippet in Python 3.6.5 that can be replicated with this simplified example below and I do not understand if this is something concerning or not. I am surprised it works honestly...
class Foo:
def bar(numb):
return numb
A1 = bar(1)
print(Foo)
print(Foo.A1)
print(Foo.bar(17))
In all python guides that I have seen, self appears as the first argument for all the purposes we know and love. When it is not, the methods are decorated with a static decorator and all is well. This case works as it is, however. If I were to use the static decorator on bar, I get a TypeError when setting A1:
Traceback (most recent call last):
File "/home/user/dir/understanding_classes.py", line 1, in <module>
class Foo:
File "/home/user/dir/understanding_classes.py", line 7, in Foo
A1 = bar(1)
TypeError: 'staticmethod' object is not callable
Is this something that is OK keeping in the code or is this a potential problem? I hope the question is not too broad, but how and why does this work?

The first parameter of the method will be set to the receiver. We call it self by convention, but self isn't a keyword; any valid parameter name would work just as well.
There's two different ways to invoke a method that are relevant here. Let's say we have a simple Person class with a name and a say_hi method
class Person:
def __init__(self, name):
self.name = name
def say_hi(self):
print(f'Hi my name is {self.name}')
p = Person('J.K.')
If we call the method on p, we'll get a call to say_hi with self=p
p.say_hi() # self=p, prints 'Hi my name is J.K.'
What you're doing in your example is calling the method via the class, and passing that first argument explicitly. The equivalent call here would be
Person.say_hi(p) # explicit self=p, also prints 'Hi my name is J.K.'
In your example you're using a non-static method then calling it through the class, then explicitly passing the first parameter. It happens to work but it doesn't make a lot of sense because you should be able to invoke a non-static method by saying
f = Foo()
f.bar() # numb = f, works, but numb isn't a number it's a Foo
If you want to put a function inside of a class that doesn't have a receiver, that's when you want to use #staticmethod (or, #classmethod more often)
class Person:
def __init__(self, name):
self.name = name
def say_hi(self):
print(f'Hi my name is {self.name}')
#staticmethod
def say_hello():
print('hello')
p = Person('J.K.')
Person.say_hello()
p.say_hello()

Related

python child class method calling overridden classmethod with non-classmethod

I am trying to do the following in python3:
class Parent:
#classmethod
def show(cls, message):
print(f'{message}')
#classmethod
def ask(cls, message):
cls.show(f'{message}???')
class Child(Parent):
#property
def name(self):
return 'John'
def show(self, message):
print(f'{self.name}: {message}')
instance = Child()
instance.ask('what')
But it then complains
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in ask
TypeError: Child.show() missing 1 required positional argument: 'message'
even so child.show works as expected. So it seems that child.ask is calling Parent.show... I tried to mark Child.show as classmethod too, but then the cls.name is not showing the expected output:
class Child2(Parent):
#property
def name(self):
return 'John'
#classmethod
def show(cls, message):
print(f'{cls.name}: {message}')
instance2 = Child2()
instance2.ask('what')
this shows
<property object at 0xfc7b90>: what???
Is there a way to override a parent classmethod with a non-classmethod, but keeping other parent classmethod to call the overridden one?
I found it hard to follow for the second half of the question but there was an issue I saw and it might help you solve your problem.
When you said even so child.show works as expected. So it seems that child.ask is calling Parent.show, thats not what is happening.
When you called instance.ask("what"), it called the #classmethod decorated method of the Child class (which is inherited from the parent). This ask method is passing the class Child as the first argument, (not the instance you created). This means the line
cls.show(f'{message}???')
is equivalent to
Child.show(f'{message}???') # because cls is the Class not the instance
The show method inside the Child class is an instance method and expects the first argument to be the actual instance (self) but the string f'{message}???' is being passed to it and it expects a second message string to be passed so that's why its is throwing an error.
Hope this helped

user defined class serialization and deserialization in python

I am very new to python : I want to serialize and deserialize my custom object in python. Please guide me on the same. I have a sample class :
import pickle
import json
class MyClass():
variable = "blah"
num = 10
def function(self):
print("this is a message inside the class.")
def get_variable():
return variable
def get_num():
return num
def main():
myObj = MyClass()
with open('/opt/infi/deeMyObj.txt', 'w') as output:
pickle.dump(myObj, output,pickle.HIGHEST_PROTOCOL)
with open('/opt/infi/deeMyObj.txt', 'r') as input:
myObjread = pickle.load(input)
print myObjread.get_variable()
print myObjread.get_num()
main()
I am getting following error :
Traceback (most recent call last):
File "sample.py", line 30, in
main()
File "sample.py", line 27, in main
print myObjread.get_variable()
TypeError: get_variable() takes no arguments (1 given)
Main intention is to read the object back.
To expand on jasonharper's comment, your get_variable and get_num methods aren't referring to the class's member variables. They should take the object as their first argument, e.g.
class MyClass:
...
def get_variable(self):
return self.variable
I think your serialization code is OK, but I might be wrong.
(Aside)
This is a bit off-topic, but another thing to note: when you define variables directly within the class block, they're defined on the class, not on objects of that class. That happens to work out in this case, since Python will look for a class-level variable of the same name if it can't find one on the object. However, if you store, say, a list in one of them and start modifying it, you'd end up sharing it between objects, which is probably not what you want. Instead you want to define them on in an __init__ method:
class MyClass:
def __init__(self):
self.variable = "blah"

My classes think that "self" is an argument that needs a value assigned

I'm not sure why this is happening. It seems to think that "self" requires an argument, which doesn't make any sense.
Here's my code:
class Animal:
def __init__(self):
self.quality = 1
class Bear(Animal):
def __init__(self):
Animal.__init__(self)
def getImage(self):
return "bear.ppm"
class Fish(Animal):
def __init__(self):
Animal.__init__(self)
def getImage(self):
return "fish.ppm"
And the error I get is:
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
Bear.getImage()
TypeError: getImage() takes exactly 1 argument (0 given)
You have to instantiate Bear before you call getImage():
b = Bear()
b.getImage()
getImage is an instance method, so it is only designed to be called on a specific instance of the Bear class. The state of that instance is what is passed as the self variable to getImage. Calling b.getImage() is equivalent to this:
b = Bear()
Bear.getImage(b)
So, without an instance of Bear, there is nothing that can be used for the self argument, which is why you see that exception when you called Bear.getImage(). See the documentation on Python instance methods for more information.
If you want to be able to call getImage on the class Bear rather than on a specific instance, you need to make it a static method, using the #staticmethod decorator:
class Bear(Animal):
def __init__(self):
Animal.__init__(self)
#staticmethod
def getImage():
return "bear.ppm"
Then you could call Bear.getImage().
getImage() is an instance method, so it can only be called with a instantiation of Bear class. So here is how you can do it:
Bear().getImage()
or
be = Bear()
be.getImage()

Use `object` to instantiate custom class

here is my haha class
class haha(object):
def theprint(self):
print "i am here"
>>> haha().theprint()
i am here
>>> haha(object).theprint()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object.__new__() takes no parameters
why haha(object).theprint() get wrong output?
class haha(object): means that haha inherits from object. Inheriting from object basically means that it's a new-style class.
Calling haha() creates a new instance of haha and thus calls the constructor which would be a method named __init__. However, you do not have one so the defaul constructor is used which does not accept any parameters.
This example of a slight change of your haha may help you to understand what is happening. I've implemented __init__ so you can see when it is called.
>>> class haha(object):
... def __init__(self, arg=None):
... print '__init__ called on a new haha with argument %r' % (arg,)
... def theprint(self):
... print "i am here"
...
>>> haha().theprint()
__init__ called on a new haha with argument None
i am here
>>> haha(object).theprint()
__init__ called on a new haha with argument <type 'object'>
i am here
As you can see, haha(object) ends up passing object as a parameter to __init__. Since you hadn't implemented __init__, you were getting an error because the default __init__ does not accept parameters. As you can see, it doesn't make much sense to do that.
You're confusing Inheritance with initializing a class when instantiate.
In this case, for your class declaration, you should do
class haha(object):
def theprint(self):
print "i am here"
>>> haha().theprint()
i am here
Because haha(object) means that haha inherits from object. In python, there is no need to write this because all classes inherits from object by default.
If you have an init method which receives parameters, you need to pass those arguments when instantiating, for example
class haha():
def __init__(self, name):
self.name=name
def theprint(self):
print 'hi %s i am here' % self.name
>>> haha('iferminm').theprint()
hi iferminm i am here

How can I get child classes to use parent variables without redefining them?

Long time reader, first time asker. Anyway, Here's the code I'm working with:
class Person(object):
def __init__(self, s):
self.name = s
self.secret = 'I HAVE THE COOKIES'
#classmethod
def shout(self):
print self.name.upper()
class Kid(Person):
def __init__(self, s):
super(Kid,self).__init__(s)
self.age = 12
b = Person('Bob')
k = Kid('Bobby')
print b.name
print k.name
print k.age
print k.secret
k.shout()
Which results in this output and error:
Bob
Bobby
12
I HAVE THE COOKIES
Traceback (most recent call last):
File "a.py", line 22, in <module>
k.shout()
File "a.py", line 8, in shout
print self.name.upper()
AttributeError: type object 'Kid' has no attribute 'name'
I assumed that Kid would be able to use the Person's shout method substituting its (the kid's) "self" for parent (where the method lives). Apparently, that's not the case. I know I could declare name outside of init, but that's both unable to accomodate inputted data and a no-no. Another alternative would be to redefine shout for every child of Person, but that's a lot of repeated code that I'm trying to avoid.
Thanks very much in advance!
The issue is that #classmethod is a method on a class. It does not have access to an instance's attributes. Specifically the method is actually passed the class object, thus self is misnamed. You should really call shout's argument cls. If you remove the #classmethod then this would all make sense and your code would work as expected.
As it is, you can think of k.shout() as equivalent to Kid.shout().

Categories