Strange (for me) error in Python OOP - python

I'm a newbie and I'm having a really strange (like in the title, for me) problem.
This is the code:
from random import shuffle
class Carta:
listaSemi=[" ","Bastoni","Spade","Coppe","Denari"]
listaValori=[" ","Asso","Due","Tre","Quattro",
"Cinque","Sei","Sette","Otto","Nove","Dieci"]
def __init__(self,seme,valore):
self.seme=seme
self.valore=valore
def __str__(self):
s1=self.listaValori[self.valore]
s2=self.listaSemi[self.seme]
return " ".join((s1,"di",s2))
class Mazzo:
def __init__(self):
self.Carte=[]
def Crea(self):
for seme in range(1,5):
for valore in range(1,11):
self.Carte.append(Carta(seme, valore))
def Mescola(self):
shuffle(self.Carte)
When i do:
M=Mazzo
Mazzo.Crea(M)
I get:
Traceback (most recent call last):
File "<pyshell#62>", line 1, in <module>
Mazzo.Crea(M)
File "/home/administrator/Scrivania/Script/Carte.py", line 19, in Crea
self.Carte.append(Carta(seme, valore))
AttributeError: type object 'Mazzo' has no attribute 'Carte'
Thanks in advance for your help!

You are initilizing your class incorrectly. Try doing this:
m = Mazzo()
m.Crea()
So a quick explanation as to why. First of all, the first line initializes an object of type Mazzo and sets in to m (please note the '()' you need that for all methods, initialization or otherwise.) Since its only perimeter is self it can be left blank.
Next we want to call the Crea function, we do this by calling the object we just created and NOT the class itself.

You are assigning type to M instead of creating a new object. Probably you want to do this:
m=Mazzo()
m.Crea()

The problem is in how you are instantiating the class Mazzo
>>>M=Mazo
What is really assigned to M is the class object and not a Mazzo object:
>>>type(M)
<class 'type'>
Your next line is even more problematic:
>>> Mazzo.Crea(M)
That asks the Mazzo class to execute the Crea method, with a Mazzo class object as the argument. And Crea doesn't take any other arguments, let alone a class object. I suspect you may have some more reading to do about the self method argument in python.
The usual way of instantiating a new object of a given class is something like:
>>> class MyClass(object):
def __init__(self,someValue):
self.someValue = someValue
def aMethod(self)
print(self.someValue)
>>> newObject = MyClass(42)
>>> newObject.aMethod()
42
Good luck.

When you did
M=Mazzo
Mazzo.Crea(M)
What you did is Mazzo.Crea(Mazzo).
First off, M=Mazzo should be M=Mazzo(). When you call just plain Mazzo, it doesn't trigger __init__, although Mazzo() does.
FYI: calling Mazzo.Crea(Mazzo) will raise a TypeError, as in
TypeError: Crea takes exactly 1 argument (2 Given)

Related

Import module functions as staticmethods into a class

I have a python module
helpers.py
def str_to_num(s: str):
'''Converts to int if possible else converts to float if possible
Returns back the string if not possible to convert to a number.
'''
# NOTE: These are not really funcs, but classes.
funcs = [int, float]
for func in funcs:
try:
res = func(s)
break
except ValueError:
continue
else:
res = s
return(res)
I have another module string_number.py
from helpers import str_to_num
class StringNumber:
def __init__(self, s):
self.s = s
str_to_num = str_to_num
#property
def value(self):
return(self.str_to_num(self.s))
def __repr__(self):
return(f'{self.__class__.__name__}({repr(self.s)})')
>>> from string_number import StringNumber
>>> sn = StringNumber(1)
>>> sn.value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "string_number.py", line 19, in value
return(self.str_to_num(self.s))
TypeError: str_to_num() takes 1 positional argument but 2 were given
However this works when accessing the function from the class:
>>> StringNumber.str_to_num(1)
1
Q.1: Why does the str_to_num attribute require two arguments when accessing it from the instance? Is self being passed to it? If so, why?
Now, I know I can add modify the __init__ method to make it an attribute of the instance
def __init__(self, s):
self.s = s
self.str_to_num = str_to_num
Further, I can resolve this by making a class of Helper functions and then inheriting from it.
from helpers import str_to_num
class Helper:
#staticmethod
def str_to_num(s):
return(str_to_num(s))
class StringNumber(Helper):
def __init__(self, s):
self.s = s
#property
def value(self):
return(self.str_to_num(self.s))
def __repr__(self):
return(f'{self.__class__.__name__}({repr(self.s)})')
Q: 2 Is there a way to make module functions, staticmethods of a class, without using inheritance? Or is this a really bad practice?
Q: 3 Assuming I had a helpers.py module, with a large amount of module functions. To incorporate them as staticmethods into my class, what would be the best way, without making a separate Helper class?
Q.1: Why does the str_to_num attribute require two arguments when accessing it from the instance? Is self being passed to it? If so, why?
You wrote "However this works when accessing the function from the class: StringNumber.str_to_num(1)". It works because you declared your method as a static method by defining it under your class definition.
As contrary to static method, instance method does pass the instance as a first argument when it's called. So when you called instance.str_to_num(1) your str_to_num(s: str) - no matter your type hinted it as a string - received instance as s argument and complained that value 1 hasn't got variable to hold it.

Python (OOP) list append error

in need of a little insight. I have the following python code:
>>> class invader:
... def __init__(self):
// list
... self.parameters = []
...
... def parameters(self):
... param = self.parameters
... param.append('3')
...
>>> invade = invader()
>>> invade.parameters()
Running this in terminal produces the following error message:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable
How can I solve this?
You problem is using the same name for your attribute and method, rename self.parameters to self.param and use self.param in your method:
class invader:
def __init__(self):
self.param = []
def parameters(self):
self.param.append('3')
invade = invader()
invade.parameters()
print(invade.param)
In the last line:
invade.parameters()
You are effectively using the list parameters as a function. (Note () at the end)
Do a
print invade.parameters
will let you see the content of the list and remove the runtime error
Both your method and attribute contain the same name parameters so you can do as follows here:
def parameters(self):
self._parameters.append('3')
It's a common to encapsulate attributes with underscores, especially with methods of the same name.
Your method and attribute contain the same name parameters. Since data attributes will override method attributes with the same name. So invade.parameters is list, not function. You should rename your function, such as append_parameters.
If you want to call parameters function, you can try this way:invader.parameters(invade).But it's not recommended

TypeError: 'int' object is not callable- Sorry if AP

class Car:
# constructor
def __init__(self, make, model, year, mpg):
# instance variables
self.carMake = make
self.carModel=model
self.carYear = year
self.efficiency=mpg
self.gas = 0
# special method
def __str__(self):
return "%s %s %s"%(self.carYear, self.carMake, self.carModel)
def refuel(self,gallon):
if gallon < 0:
print("Sorry, amount cannot be negative")
else:
self.gas=self.gas+gallon
print (self.gas)
print("Added %.2f gallon of gas to the tank"%(self.gas))
def gas(self):
print(self.gas)
> Traceback (most recent call last): File "<pyshell#12>", line 1, in
> <module>
> c1.gas() TypeError: 'int' object is not callable
Your method gas and your instance attribute gas created in __init__ have the same name. The method is stored on the class, but is "shadowed" by the attribute stored on the instance, since Python first looks for names on the instance, then on the class and its parents.
So self.gas is an integer and you can't call it.
You have self.gas initialized to an int in the __init__() method, but then you define a method named gas() as well. Once __init__() runs, self.gas is an int. I'm guessing somewhere you are calling gas() on an instance of this class.
Rename your gas() method to something like print_gas(), or, wherever you're calling this, instead of doing c1.gas(), just do print c1.gas.
Consider this class Test in a file called test.py:
class Test:
def __init__(self):
self.x=3
def x(self):
print self.x
Now I import class Test in my console and see what methods it has:
>>> from test import Test
>>> [method for method in dir(Test) if callable(getattr(Test, method))]
['__init__', 'x']
Notice that it has the method x. Now let's create an instance of Test
>>> k=Test()
Let's see what methods we have
>>> [method for method in dir(k) if callable(getattr(k, method))]
['__init__']
>>>
As you can see the method x is no longer available. why?
When you created k as an instance of Test, it executes the __init__ method and sees self.x=3 which redefines x to be just a variable in self and your method x() is gone. So when you do k.x() it thinks that you are doing it on self.x that you set in __init__ which is not callable. However just k.x will work as I show below:
>>> k.x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>> k.x
3
>>>
The conclusion is don't name your variables and methods the same.

exec to add a function into a class

So I've looked at similar questions, and I've found some solutions to this, but I can't quite figure out how to do this.
What I'm trying to do is add a method to a class from a string. I can do this with the setattr() method, but that won't let me use self as an attribute in the extra method. Here's an example: (and I apologize for the variable names, I always use yolo when I'm mocking up an idea)
class what:
def __init__(self):
s = 'def yolo(self):\n\tself.extra = "Hello"\n\tprint self.extra'
exec(s)
setattr(self,"yolo",yolo)
what().yolo()
returns this:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: yolo() takes exactly 1 argument (0 given)
and if s = 'def yolo():\n\tself.extra = "Hello"\n\tprint self.extra'
then I get this result:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 2, in yolo
NameError: global name 'self' is not defined
This essentially means that I cannot dynamically create methods for classes, which I know is bad practice and unpythonic, because the methods would be unable to access the variables that the rest of the class has access to.
I appreciate any help.
You have to bind your function to the class instance to turn it into a method. It can be done by wrapping it in types.MethodType:
import types
class what:
def __init__(self):
s = 'def yolo(self):\n\tself.extra = "Hello"\n\tprint self.extra'
exec(s)
self.yolo = types.MethodType(yolo, self)
what().yolo()
On a side note, why do you even need exec in this case? You can just as well write
import types
class what:
def __init__(self):
def yolo(self):
self.extra = "Hello"
print self.extra
self.yolo = types.MethodType(yolo, self)
what().yolo()
Edit: for the sake of completeness, one might prefer a solution through the descriptor protocol:
class what:
def __init__(self):
def yolo(self):
self.extra = "Hello"
print self.extra
self.yolo = yolo.__get__(self)
what().yolo()
Another way, seems more elegant to me:
class what:
pass
ld = {}
exec("""
def yolo(self):
self.extra = "Hello"
print(self.extra)
""", None, ld)
# print('locals got: {}'.format(ld))
for name, value in ld.items():
setattr(what, name, value)
what().yolo()

Issues using self as a method argument in python

I apologize for the basic question but I am having some issues with either syntax or the overall concept I think. The problem I am having is that when I call the method in the class below the interpreter wants me to input a value for the self argument which I don't think is supposed to happen. What I am trying to do is create an object to hold several tkinter widgets at once so that I can dynamically add or subtract them in groups rather than one at a time. Any help here is greatly appreciated, thanks!
class User(object):
#Input a UI row number and this will generate a corresponding row of widgets
def generateLine(self, rowNumber):
self.NameVar = StringVar()
self.ReasonVar = StringVar()
#ExcusedVar
self.Name_Cbox = ec.AutocompleteCombobox(content, textvariable = self.NameVar)
self.Name_Cbox.grid(row = rowNumber, column = 0)
self.Reason_Cbox = ec.AutocompleteCombobox(content, textvariable = self.ReasonVar)
self.Reason_Cbox.grid(row = rowNumber, column = 1)
Make sure you have an instance of your User class to call the method on:
user = User()
user.generateLine(0)
self is only provided when the method has been bound to an instance.
If you call the method directly on the class, you'll get an exception:
>>> class User(object):
... def generateLine(self, row):
... print row
...
>>> User.generateLine(0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method generateLine() must be called with User instance as first argument (got int instance instead)
>>> User().generateLine(0)
0

Categories