The following is a simplified example of a problem I ran into recently, and even though I circumvented it by rewriting my code, I still do not understand why it happened.
Consider the following python script:
class Configer(object):
def __init__(self, pops_cfg = {}):
self.pops_cfg = pops_cfg
def add_pop(self, name, gs):
pop_cfg = {}
pop_cfg['gs'] = gs
if name in self.pops_cfg:
self.pops_cfg[name].update(pop_cfg)
else:
self.pops_cfg[name] = pop_cfg
def get(self):
return self.pops_cfg
def get_config(name):
if name == 'I':
c = Configer()
c.add_pop('I', 100)
elif name == 'IE':
c = get_config('I')
c.add_pop('E', 200)
else:
raise
return c
with these test cases:
# test 1
a = get_config('I')
print(a, id(a), a.get())
# <__main__.Configer object at 0x7fa83615c580> 140360438629760 {'I': {'gs': 100}}
# test 2
b = get_config('IE')
print(b, id(b), b.get())
# <__main__.Configer object at 0x7fa83603f610> 140360437462544 {'I': {'gs': 100}, 'E': {'gs': 200}}
# test 3
c = get_config('I')
print(c, id(c), c.get())
# <__main__.Configer object at 0x7fa83603c280> 140360437449344 {'I': {'gs': 100}, 'E': {'gs': 200}}
The first and second tests behave as I expect, adding two dictionaries with keys I and [I,E], respectively. However, the last one, which is identical to the first, surprisingly (to me) has both keys. I understand what is happening, but don't understand why.
What is happening
Here, even in the second test has some flaws. In it the internal get_config('I') call does not create a new instance of the Configer object. Instead, it updates the one created earlier for the first test a = get_config('I'). Similarly, the last test does not create a new object but updates the previously created one, i.e. the variable c which has both I and E as its pops_cfg keys after execution of the second test.
What I don't understand
I'm curious to know why bolded the part in the last paragraph is happening. In particular, I thought the way the if/elif statements is written, would force both name==I and name==IE tests to construct a base case (associated with name==I) and then update it according to the needs. It's not happening though.
Can you please enlighten me?
Related
This question already has answers here:
What do lambda function closures capture?
(7 answers)
Creating functions (or lambdas) in a loop (or comprehension)
(6 answers)
Closed 1 year ago.
Originally, this question was "deepcopy a string in python" and I am keeping the essential parts of the discussion here for reference. Although I did solve the issue, I do not understand neither the origin of the issue nor how the solution differs from the wrong/not working implementation. I do want to understand this and took my time trimming the actual code into a minimal working example.
But first, part of original content: Deep copies of strings return the exact same string:
import copy
_1= "str"
_2 = copy.deepcopy(_str)
_1 is _2 # True
Novel content:
The questions are inline with the code. Basically, I am decorating a TestClass with a decorator that is going to generate standardized 'appender' methods on the class for every attribute inserted in TestClass.channels. Please note, I am not posting the question to discuss software architecture/implementation, but to actually understand the interstitials of Python relevant to the noted issue: why all 'appenders' generated will actually perform exactly the same work as that prepared for the last attribute of TestClass.channels defined:
import pandas as pd
def standardize_appenders(kls):
# Checks `kls` for `channels`, whether an appropriate pseudo implementation of 'appenders' is
# available and actually implements the `appenders`: BESIDES AUTOMATION, I AM FORCING A STANDARD
channels = getattr(kls, 'channels')
chs = [o for o in dir(channels) if not o.startswith('_')]
chs_type = [getattr(channels, o) for o in chs]
for _ch, _ch_type in zip(chs, chs_type): # ISSUE SOMEWHERE HERE
ch = _ch
ch_type = _ch_type
# THE `def appender(self, value, ch=ch, ch_type=ch_type)` SIGNATURE SOLVES THE PROB BUT I DON'T UNDERSTAND WHY
def appender(self, value):
# nonlocal ch, ch_type
if not isinstance(value, ch_type):
raise TypeError(f'`{ch}` only accepts values of `{ch_type}` type, but found {type(value)}')
data: dict = getattr(self, 'data') # let's not discuss the need for this knowledge please
data_list = data.setdefault(ch, [])
data_list.append(value)
data.setdefault(ch, data_list)
appender.__doc__ = f'<some {ch}/{ch_type}> specific docstring'
setattr(kls, 'append_' + ch, appender) # ALL APPENDERS OF `kls` WILL DO ESSENTIALLY THE WORK DEFINED LAST
return kls
#standardize_appenders
class TestClass:
class channels:
dataframe = pd.DataFrame
text = str
def __init__(self):
self.data = {}
if __name__ == '__main__':
test_inst = TestClass()
# test_inst.append_dataframe(pd.DataFrame({"col1": [1, 2, 3], "col2": list("abc")}))
# TypeError: `text` only accepts values of `<class 'str'>` type, but found <class 'pandas.core.frame.DataFrame'>
test_inst.append_dataframe("A minimal trimmed example") # NOTE THE WRONG 'APPENDER'
test_inst.append_text("of the original implementation")
print(test_inst.data)
# Out {'text': ['A minimal trimmed example', 'of the original implementation']}
Be my guest to propose a better title. I am out of good ideas for a short, but informative title for this case.
(Windows 10, python 3.8 from an anaconda env)
I have a unit test that works as intended, however I feel that this is not the best way to test multiple inputs with pytest. It definitely violates the DRY principle. I thinking there's a better way to go about this but I can't figure out what. I'm also not sure what to actually do with the mock. It's not used but it has to be there (see 'mock_choice' parameter in the function in the code below).
I thought perhaps looping through the calls would work but that didn't work as intended. I really couldn't figure out another way besides using side_effects and calling four times to test to make sure I get return value as I intended.
Function To Test
def export_options():
while True:
try:
choice = int(input("\nPlease make a selection"))
if choice in ([option for option in range(1, 5)]):
return choice # This what I'm testing
else:
print("\nNot a valid selection\n")
except ValueError as err:
print("Please enter an integer")
Test Function
#mock.patch('realestate.app.user_inputs.input', side_effect=[1, 2, 3, 4])
def test_export_options_valid_choice(mock_choice): # mock_choice needs to be here but isn't used!
export_option = user_inputs.export_options()
assert export_option == 1
export_option = user_inputs.export_options()
assert export_option == 2
export_option = user_inputs.export_options()
assert export_option == 3
export_option = user_inputs.export_options()
assert export_option == 4
The test works. It passes as the function returns all values between 1 and 4. However, because the code is very repetitive, I would like to know if there's a better way to test multiple input calls as I would like to apply the same to future tests.
You can use a for loop to avoid repeating code.
#mock.patch('realestate.app.user_inputs.input')
def test_export_options_valid_choice(self, mock_choice):
for response in [1, 2, 3, 4]:
with self.subTest(response=response)
mock_choice.return_value = response
export_option = user_inputs.export_options()
self.assertEqual(export_option, response)
# Not in original version, but other tests might need it.
mock_choice.assert_called_once_with("\nPlease make a selection")
mock_choice.reset_mock()
The subtest context manager will tell you which input failed.
Is the only way to do something like this is with sub-test using the unittest module? I know pytest doesn't support subtests, but I was hoping there was a similar type of hack.
You can certainly loop without using subtests, but you may have difficulty telling which input failed. More generally, instead of a loop, you can call a common helper function for each test.
For pytest in particular, you can use the #pytest.mark.parametrize decorator to automate this.
#pytest.mark.parametrize('response', [1, 2, 3, 4])
#mock.patch('realestate.app.user_inputs.input')
def test_export_options_valid_choice(mock_choice, response):
mock_choice.return_value = response
export_option = user_inputs.export_options()
assert export_option == response
I know the motto is "we're all consenting adults around here."
but here is a problem I spent a day on. I got passed a class with over 100 attributes. I had specified one of them was to be called "run_count". The front-end had a place to enter run_count.
Somehow, the front-end/back-end package people decided to call it "run_iterations" instead.
So, my problem is I am writing unit test software, and I did this:
passed_parameters.run_count = 100
result = do_the_thing(passed_parameters)
assert result == 99.75
Now, the problem, of course, is that Python willingly let me set this "new" attribute called "run_count". But, after delving 10 levels down into the code, I discover that the function "do_the_thing" (obviously) never looks at "run_count", but uses "passed_paramaters.run_iterations" instead.
Is there some simple way to avoid allowing yourself to create a new attribute in a class, or a new entry in a dictionary, when you naievely assume you know the attribute name (or the dict key), and accidentally create a new entry that never gets looked at?
In an ideal world, no matter how dynamic, Python would allow you to "lock" and object or instance of one. Then, trying to set a new value for an attribute that doesn't exist would raise an attribute error, letting you know you are trying to change something that doesn't exist, rather than letting you create a new attribute that never gets used.
Use __setattr__, and check the attribute exists, otherwise, throw an error. If you do this, you will receive an error when you define those attributes inside __init__, so you have to workaround that situation. I found 4 ways of doing that. First, define those attributes inside the class, that way, when you try to set their initial value they will already be defined. Second, call object.__setattr__ directly. Third, add a fourth boolean param to __setattr__ indicating whether to bypass checking or not. Fourth, define the previous boolean flag as class-wide, set it to True, initialize the fields and set the flag back to False. Here is the code:
Code
class A:
f = 90
a = None
bypass_check = False
def __init__(self, a, b, c, d1, d2, d3, d4):
# 1st workaround
self.a = a
# 2nd workaround
object.__setattr__(self, 'b', b)
# 3rd workaround
self.__setattr__('c', c, True)
# 4th workaround
self.bypass_check = True
self.d1 = d1
self.d2 = d2
self.d3 = d3
self.d4 = d4
self.bypass_check = False
def __setattr__(self, attr, value, bypass=False):
if bypass or self.bypass_check or hasattr(self, attr):
object.__setattr__(self, attr, value)
else:
# Throw some error
print('Attribute %s not found' % attr)
a = A(1, 2, 3, 4, 5, 6, 7)
a.f = 100
a.d1 = -1
a.g = 200
print(a.f, a.a, a.d1, a.d4)
Output
Attribute g not found
100 1 -1 7
Here I created a module.
class Employee:
def __init__(self):
self.name = input("Enter your name: ")
self.account_number = int(input("Enter your account number: "))
def withdraw(self): # it receives values from for
if withdraw1 > current_balance:
print ("You have entered a wrong number: ")
else:
print ("The current balance is: ", current_balance - withdraw1)
import TASK2 # I am importing the module I created
c = TASK2.Employee()
def for(self):
c.withdraw1 = int(input("enter number: "))
c.current_balance = int(input("Enter the current balance: "))
d = method(c.withdraw) # here I am trying to pass the values to withdraw
print (d)
The problem I get is that although it asks for the values instead of giving me an answer it gives me None.
Here's my take on your code.
# TASK2.py
class Employee:
def __init__(self):
self.name = input("Enter your name: ")
self.account_number = int(input("Enter your account number: "))
# make sure you initialise your member variables!
self.withdraw_val = 0 # withdraw1 is ambiguous, so I use withdraw_val instead
self.current_balance = 0
# receives values from for ### no it doesn't, right now, it GIVES values TO your "for" function
def withdraw(self):
if self.withdraw_val > self.current_balance: # remember to use "self." to
# access members within the class
print ("You have entered a wrong number: ")
else:
# again, remember "self."
print ("The current balance is: ", self.current_balance - self.withdraw_val)
# TASK2sub.py
import TASK2
c = TASK2.Employee()
def for_employee(employee): # (1) don't use "self" outside a class
# it's contextually unconventional
# (2) "for" is a keyword in Python, don't use it for naming
# variables/functions, it'll mess things up
employee.withdraw_val = int(input("Enter value to withdraw: "))
employee.current_balance = int(input("Enter the current balance: "))
return employee.withdraw_val # not entirely sure what you want to return
# but you should definitely return something
# if you're going to assign it to some variable
d = for_employee(c.withdraw()) # "for_employee" function needs a return statement
# ".withdraw()" method should also require a return statement
print(d)
Note: I'll be referring to your original for function as for_employee from now on. Also note that I'm still hazy about what you're trying to accomplish and that there is most probably a more suitable name for it.
Since your original for_employee function didn't return anything, it returns None by default. (This explains the output you saw.)
I think you're misunderstanding how functions work in general. For example,
d = for_employee(c.withdraw())
print(d)
Your comment for the .withdraw() method is inaccurate.
"it receives values from for"
More accurately, c.withdraw() will first be computed, then whatever it returns is passed into the for_employee function as a parameter. Instead of "receiving values from", the withdraw method "gives values to" the for_employee function.
Something more reasonable would be
c.withdraw() # on a line by itself, since it doesn't return anything
d = for_employee(c) # pass the entire object, since you'll be using self.withdraw_val and whatnot
print(d)
Another issue is with conventional naming. This is what I get from the IDLE (with Python 3.7) when defining a function named for
>>> def for(a): return a
SyntaxError: invalid syntax
Again, for is a keyword in Python, don't use it for naming your variables, functions, or classes.
With self, it's less severe (but I could see that it's confusing you). self is more of a convention used in class methods. But for_employee isn't a class method. So conventionally speaking, the parameter shouldn't be named self.
(I find the code spaghetti-ish, it might benefit if you refactor the code by moving the for_employee method into the class itself. Then it would completely make sense to use self.)
In short; I started coding a few days ago and thought trying to make a simple text based adventure will let me face a lot of problems that I will encounter in other harder projects as well. My class init function produces items with some variables, one of which is it's equipment slot position [0-6]. I would like to unequip a slot, but the way I have it set up at the moment requires me to know which item is in that particular slot.
In english: unequip("mainhand"), mainhand has slotnumber 0. Get the info of all equipped items and check which one has the corresponding slotnumber, then remove that particular item.
(Some items have 2 slotnumbers ("althand") which means I will have to find a way to make sure I remove the right item from the list, but that is something I can do later). For now, I can't seem to figure out how to dynamically call items and do stuff with them.
PS. I am pretty sure I can do this in a way more phytonic manner and any suggestions are welcome, but regardless of this, I would still like to know how to dynamically call the function.
The code with which I try this:
def Unequip(Slotname): #Slotname is just a string, so I could say: unequip("Mainhand")
for count,i in enumerate(Item.slotname): #Item.slotname is a list of strings for slots which corresponds with Item.Slot which are the values determining the which slot is occupied.
if Slotname == Item.slotname[count]: #so when the name I put into the function equals the Item.slotname, I know which number in Item.Slot I need.
for items in Item: #Here we enter the problem (again, I understand that this code could be a lot better and would love some suggestions).
#Item is a object, but it's __init__ functions produces items, such as item1, item2 etc. I would like to check if any of these items is currently in my Item.Equipped and has the Item.Slot value I want to remove.
#I tried global(), locals() and dir(Item) but none give me what I need. I really hope this makes it clear.
if Item.Slot[count] == items.slot and items.name == Item.Equipped: #I need a susbtitute for the items variable which will represent item1, item2 or item3 etc. So I can do Item.Slot.remove(item2.slot).
Slot = Item.Slot.remove(items.slot)
Equipped = Item.Equipped.remove(items.name)
Player.stats = list(map(operator.sub,list(Player.stats),self.itemstats))
elif Item.Slot[i] == items.altslot and Items.name == items.Equipped:
pass
Full code (I tried using self etc, but it may not be super readable, my apologies), it includes a item.unequip function but this requires me to select the particular item instead of just the slot from which I want my item to be removed
Edit1: Removed all unneeded stuff per request:
import random
import operator
class Item:
Equipped = []
Slot = []
Inventory = []
num_of_items = 0
statnames = ["Strength", "Agility", "Dexterity", "Wisdom", "Constitution", "Intelligence"]
slotname = ["MainHand","Offhand","Head", "Necklace","Chest","Legs", "Cape" ]
def __init__(self, name, itemstats, slot, altslot = None, atk = None, Def = None):
self.itemstats = itemstats #itemstats in order: S, A, D, W, C, I
self.name = name
Item.num_of_items += 1
self.slot = slot
self.altslot = altslot
if atk != None and atk != 0:
self.atk = atk
if Def != None and Def != 0:
self.Def = Def
def Unequip(Slotname):
for count,i in enumerate(Item.slotname):
if Slotname == Item.slotname[count]:
for items in dir(Item):
if Item.Slot[count] == items.slot and items.name == Item.Equipped:
Slot = Item.Slot.remove(items.slot)
Equipped = Item.Eqiupped.remove(items.name)
Player.stats = list(map(operator.sub,list(Player.stats),self.itemstats))
elif Item.Slot[i] == items.altslot and Items.name == items.Equipped:
pass
class Player:
stats= [8,8,8,8,8,8]
item1 = Item("Sword of damaocles",[5, 1, 0,1,2,-2],0,1,20)
item2 = Item("Helmet of steel",[9,9,9,9,9,9],2,None,0,20)