default arg with test class member variable - python

Have the simplified script snippet as follows:
#!pytest
import pytest
class TestDefaultArg():
#pytest.fixture(scope="class", autouse=True)
def setup_cleanup(self, request):
request.cls.default_id = 100
yield
def test_basic(self):
self.db_helper()
def db_helper(self, id=self.default_id):
pass
The intention is to pass the class memeber self.default_id to db_helper(). However, it gives me this error:
/u/qxu/test.py:4: in <module>
class TestDefaultArg():
/u/qxu/test.py:13: in TestDefaultArg
def db_helper(self, id=self.default_id):
E NameError: name 'self' is not defined
So, the question is, how to use a test class data member to provide the default argument for a test class member function?

This is not related to pytest, but just to normal Python functionality.
You cannot reference self in a default argument, because the default argument is created at load time, not at run time. Also, you have been creating a class variable, not an instance variable, so you have to access it using self.__class__ instead of self.
The usual way to handle this is to use a None default value and only set the value in the function, e.g. something like:
def db_helper(self, current_id=None):
if current_id is None:
current_id = self.__class__.default_id
print(current_id)
Note that I have also changed the variable name, as id is a built-in name, and it is not good practice to shadow these.
EDIT: Use check for None as proposed by #chepner.

Related

class attribute default value doesn't receive the mock

I'm unit testing a module I wrote and encountering a problem with default class object provided to a function that has a mock for it.
This is how it looks in high level:
main_file.py
class MainClass(object):
def main_func(self):
sub_class_obj = SubClass()
sub_class_obj.sub_func()
sub_file.py
class SubClass(object):
def sub_func(self, my_att=Helper(2)):
self.my_att = my_att
helpers.py
class Helper():
def __init__(self, my_val):
self.my_val = my_val
test.py
class TestClass(object):
#patch('sub_file.Helper', MockHelper)
def my_test(self):
main_class_obj = MainClass()
main_class_obj.main_func()
When I do that in a way that my_att is provided - all works well and the Mock is called, but when I don't and the default value is set - I get the original Helper class object.
Any idea how to make the default value for this attribute to receive the mock as well?
Thanks in advance!
The problem is that the default value is read at import time, so it is already set in the function before you patch Helper. The defaults are saved in the function object at that point.
You can, however, also patch the default arguments of your function (which can be accessed via __defaults__:
from sub_file import SubClass
class TestClass(object):
#patch('sub_file.Helper', MockHelper)
#patch.object(SubClass.sub_func, "__defaults__", (MockHelper(),))
def my_test(self):
main_class_obj = MainClass()
main_class_obj.main_func()
Note that __defaults__ has to be a tuple of arguments.
You could also use monkeypatch to do the same:
from sub_file import SubClass
class TestClass(object):
#patch('sub_file.Helper', MockHelper)
def my_test(self, monkeypatch):
monkeypatch.setattr(SubClass.sub_func, "__defaults__", (MockHelper(),)
main_class_obj = MainClass()
main_class_obj.main_func()
UPDATE:
I didn't realize that this would not work with Python 2. Apart from the other name of the default arguments (func_defaults instead of __defaults__) this would only work with standalone functions, but not with methods, as setattr is not supported in this case in Python 2. Here is a workaround for Python 2:
from sub_file import SubClass
class TestClass(object):
#patch('sub_file.Helper', MockHelper)
def my_test(self):
orig_sub_func = SubClass.sub_func
with patch.object(SubClass, "sub_func",
lambda o, attr=MockHelper(): orig_sub_func(o, attr)):
main_class_obj = MainClass()
main_class_obj.main_func()
This way, the original sub_func is replaced by a function that has its own default value, but otherwise delegates the functionality to the original function.
UPDATE 2:
Just saw the answer by #chepner, and it is correct: the best way would be to refactor your code accordingly. Only if you cannot do this, you try this answer.
Default values are created when the function is defined, not when it is called. It's too late to patch Helper in your test, because SubClass.__init__ has already been defined.
Rather than patching anything, though, re-write MainClass so that there is no hard-coded reference to SubClass: then you can create the proper instance yourself without relying on a default value.
You can pass an instance directly:
class MainClass(object):
def main_func(self, sub_class_obj):
sub_class_obj.sub_func()
class TestClass(object):
def my_test(self):
mock_obj = MockHelper()
main_class_obj = MainClass(mock_obj)
main_class_obj.main_func()
or take a factory function that will be called to create the subclass object.
class MainClass(object):
def main_func(self, factory=SubClass):
sub_class_obj = factory()
sub_class_obj.sub_func()
class TestClass(object):
def my_test(self):
main_class_obj = MainClass(lambda: SubClass(MockHelper()))
main_class_obj.main_func()

Why can't I call a method from my Python class?

I am learning Python and currently working with classes. I am trying to make a basic game to help learn it and am having a weird issue with calling methods
from it. I have the main.py file which creates an instance from the class in the Character.py file.
This is the Character.py file:
class Character:
name=""
def __init__(Name):
name=Name
def getName():
return name
This is the main.py file:
from Character import *
player = Character("James")
print(player.getName())
I am not sure what the issue is. This is the error I get:
Traceback (most recent call last):
File "C:\Users\dstei\Documents\Python\It 102\Final Project\Main.py", line
12, in <module>
print(player.getName())
TypeError: getName() takes 0 positional arguments but 1 was given
It is saying I am giving 1 positional argument but I don't see where I gave any. What am I missing?
Since you have a class with instance methods, you need to include the first argument (self by convention) to refer to the current instance. Also, make sure to set the variable as an instance variable by using self, the current instance:
class Character:
def __init__(self, Name): #self is the current instance
self.name=Name #set the variable on the instance so that every instance of Character has a name
def getName(self):
return self.name #refer to the name with the instance
Python internally passes the new instance of a class as the first argument to all the class methods, like this in languages such as Java. The error comes from the fact that Python passes the instance as the first argument internally but your getter is not defined to take an argument.
With the above code, when you call the method upon an instance, the instance is internally passed as the first argument and Python doesn't complain as you specify that it takes an argument, self, and name is set correctly on the instance.
Note: By convention, Python does not use camelCase, but underscores, so your getter should by convention look like this:
def get_name(self):
#...
Also see chepner's answer which explains why getters and setters aren't usually needed. Just get and modify the instance variable by using dot notation:
print(player.name) #get
player.name = "Jeff" #set
As others have mentioned, even instance method must be declared with an extra argument, typically named self (although that is a conventional, not a required, name).
class Character:
def __init__(self, name):
self.name = name
def get_name(self):
return name
However, Python does not have any kind of enforced visibility (such as public or private), so such trivial getters and setters aren't usually written. Documentation about which attributes you are "allowed" to modify are considered sufficient protection.
class Character:
def __init__(self, name):
self.name = name
c = Character("Bob")
print(c.name) # instead of c.get_name()
c.name = "Charlie" # instead of c.set_name("Charlie")
You are forgetting to add the parameter self. self is an object reference to the object itself, therefore, they are same. Python methods are not called in the context of the object itself. self in Python may be used to deal with custom object models or
class Character:
def __init__(self,name):
self.name=name
def getName(self):
return self.name
To see why this parameter is needed, there are so good answers here:
What is the purpose of self?

self parameter error celery

I saw some blogs online where they put self parameters on their celery functions, why is mine causing an error like:
TypeError: xml_gr() takes exactly 1 argument (0 given)
Here is my code:
#periodic_task(run_every=timedelta(seconds=5))
def xml_gr(self):
ftp = FTP('xxxxx')
ftp.login(user='xxxxx', passwd='xxxxx')
x = xml_operation('AGIN', GR_GLOBAL_CURRENT_DATE, ftp, "GR")
ftp.close()
In addition to the accepted answer, self is used in celery to bind tasks.
Bound tasks are needed for retries. for accessing information about
the current task request, and for any additional functionality
you add to custom task base classes.
So, if you specify #task(bind=True) then you need add self as the first argument. Otherwise, not needed.
Source
"self" is used within class member functions. When you call a member function in an instance of the class, the language automatically passes in the class instance as "self". "self" lets you access the members of the class.
class Thing:
var1 = None
def set_member(self, value):
self.var1 = value
def show_member(self, value):
return self.var1
Then usage would be
a = Thing()
a.set_member(23)
a.show_member()
And you'd see the response 23. You don't have to pass in the "self" variable explicitly.
When you declare functions outside of a class, there is no reason to use "self".

How to make user defined classes in web2py

I am trying to make a class within a module, import that module file in my controller, and then reference the class that is defined within that module, but I keep getting a message that reads NameError("name 'self' is not defined")
Here is my code in my created module:
from gluon import *
class device_info(object):
self.info = {}
def __init__(self, info):
self.info = info
return
def setInfo(info):
self.info = info
return
def getInfo():
return self.info`
Does anyone know what causes this and how it can be resolved? I was under the impression that user-defined classes were supported in web2py.
As stated, just move self.info = {} into __init__().
__init__() is essentially a constructor that you are familiar with from java. It initializes an instance object of that class when called. I haven't used Java in some time, but I don't think you should be be declaring class variables outside of your constructor there either.
self is an argument that all methods within a class in python must receive as their first argument. So your getters and setters are also not going to work if you try them; they must be:
def setInfo(self, info) and def getInfo(self)
When you create an object, like this:
device1 = device_info()
it calls __init()__, passing device1 as self. Then, whenever you use that object, such as
device1.setInfo(newInfo), you can think of the method in the class' context being called as setInfo(device1, newInfo), since device1 is self, or the current instance of the device_info object in use.
You also don't need the object argument at the class definition. What do you expect that to do?
Edit: Actually, don't move self.info = {} into __init__(), just get rid of it. You already have self.info = info in __init__(). You don't need to initialize variables like that in Python like you do in Java. Creating an empty dict and then setting it to another dict without any use is redundant.

Python: How to call the constructor from within member function

This Question / Answer (Python call constructor in a member function) says it is possible to to call the constructor from within a member function.
How do I do that?
Is it good a style?
I tried it with the following code:
class SomeClass(object):
def __init__(self, field):
self.field = field
def build_new(self):
self = SomeClass(True)
def main():
inst = SomeClass(False)
inst.build_new()
print(inst.field)
if __name__ == '__main__':
main()
As output I get: False
Since I called the build_new() method inst.field should be True or not?
The problem is not in calling the constructor, but what you're doing with the result. self is just a local variable: assigning to it won't change anything at all about the current instance, it will just rebind the name to point to a new instance which is then discarded at the end of the method.
I'm not totally certain what you are trying to do, but perhaps you want a classmethod?
class SomeClass(object):
...
#classmethod
def build_new(cls):
return cls(True)
SomeClass.build_new(False)
I believe what you are looking for is just calling the init function again.
class SomeClass(object):
def __init__(self, field):
self.field = field
def build_new(self):
self.__init__(True)
This will cause the field variable to be set to True over False. Basically, you are re-initializing the instance rather than creating a brand new one.
Your current code creates a new instance and just loses the reference to it when it goes out of scope (i.e. the function returning) because you are just rebinding the name of self to a different value not actually changing the inner contents of self.

Categories