I am new to Python but write programs for a hobby, so I have moderate knowledge of OOP and computer programming in general. I have started working on an simple animal simulator. In what might very well be a heathen move, I am trying to store all of the 'action functions' of the animal in a dictionary, so that each function is accessible by a string. For example, dict['SLEEP']() calls the sleep function.
I could find no examples of what I am trying to accomplish, and frankly am not sure how to intelligently describe my problem. See the bare-bones code below:
class Animal:
def __init__(self):
self.health = 100
self.actions = {} # dictionary of functions
self.initializeAnimal()
def initializeAnimal(self):
self.actions['SLEEP'] = self.initializeSleep() # add sleep function
def initializeSleep(self):
RESTORED_HEALTH = 20
# other constants
def sleep(self):
self.health += RESTORED_HEALTH
# utilize other constants
return sleep
Then, the animal handler would perform something along the following lines:
for animal in animalList:
animal.actions['SLEEP']()
I'd of course like the animal's health to increase by 20 when the sleep function is called. Instead, nothing happens. After some research and experimenting, I see that the self passed to the sleep() function apparently refers to initializeSleep() rather than the animal class.
I am at somewhat of a loss as to how I would change the health of the animal when calling functions in this manner. Do I have to somehow make use of super-class calls?
edit: clarify syntax
Python does some maneuvers so that functions defined in a class body actually behave as "methods" - and thus, get the "self" parameter added authomatically.
It is not hard to understand how that is done - and to emulate it for an explicit dictionary as you plan - but first, consider that you can retrieve a method name using a string, without resorting to storing them in dictionaries as you plan - you can simply do:
class Animal(object):
...
def sleep(self, ...):
...
my_cow = Animal()
function = getattr(my_cow, "sleep")
function ( )
# and of course, the two preceeding lines can be in a single expression:
getattr(a, "sleep")()
Now, let's see for the dicionary -
since you defien the actual "sleep" function as a nested function, it will "see" the "self" variable as it exists in the invocation of initializeSleep() - which means what you are doing should just work - as soons as you fix the call to initializeSleep() by prefixing it with the self. , as in:
def initializeAnimal(self):
self.actions['SLEEP'] = self.initializeSleep() # add sleep function
And remove the "self" parameter from the actual "sleep" function - it does not need it,
as it will "see" the nonlocal self variable in the enclosing scope:
def initializeSleep(self):
RESTORED_HEALTH = 20
# other constants
def sleep():
self.health = RESTORED_HEALTH
# utilize other constants
return sleep
(The other constants defined inside the initializeSLeep will also be visible inside sleep as nonlocal variables)
You don't need to put the self attribute into the sleep function.
Its perfectly valid to do the following:
class Animal:
def __init__(self):
self.health = 100
self.actions = {} # dictionary of functions
self.initializeAnimal()
def initializeAnimal(self):
self.actions['SLEEP'] = self.initializeSleep() # add sleep function
def initializeSleep(self):
RESTORED_HEALTH += 20
# other constants
def sleep():
self.health += RESTORED_HEALTH
# utilize other constants
return sleep
a = Animal()
print(a.health)
a.actions['SLEEP']()
print(a.health)
Output:
100
120
As stated, you forogt the += in self.health = RESTORED_HEALTH.
You also missed the self in self.initializeSleep()
Related
In python, is it possible to chain together class methods and functions together? For example, if I want to instantiate a class object and call a method on it that affects an instance variable's state, could I do that? Here is an example:
class Test(object):
def __init__(self):
self.x = 'Hello'
#classmethod
def make_upper(y):
y.x = y.x.upper()
What I'm wanting to do is this:
h = Test().make_upper()
I want to instantiate a class object and affect the state of a variable in one line of code, but I would also like to be able to chain together multiple functions that can affect state or do something else on the object. Is this possible in python like it is in jQuery?
Yes, sure. Just return self from the instance methods you are interested in:
class Test(object):
def __init__(self):
self.x = 'Hello'
def make_upper(self):
self.x = self.x.upper()
return self
def make_lower(self):
self.x = self.x.lower()
return self
h = Test().make_upper()
print(h.x)
Output:
HELLO
Yes and no. The chaining certainly works, but h is the return value of make_upper(), not the object returned by Test(). You need to write this as two lines.
h = Test()
h.make_upper()
However, PEP-572 was recently accepted for inclusion in Python 3.8, which means someday you could write
(h := Test()).make_upper()
The return value of Test() is assigned to h in the current scope and used as the value of the := expression, which then invokes its make_upper method. I'm not sure I would recommend using := in this case, though; the currently required syntax is much more readable.
I have run across a few examples of Python code that looks something like this:
class GiveNext :
list = ''
def __init__(self, list) :
GiveNext.list = list
def giveNext(self, i) :
retval = GiveNext.list[i]
return retval
class GiveABCs(GiveNext):
i = -1
def _init__(self, list) :
GiveNext.__init__(self, list)
def giveNext(self):
GiveABCs.i += 1
return GiveNext.giveNext(self, GiveABCs.i)
class Give123s(GiveNext):
i = -1
def _init__(self, list) :
GiveNext.__init__(self, list)
def giveNext(self):
Give123s.i += 1
return GiveNext.giveNext(self, Give123s.i)
for i in range(3):
print(GiveABCs('ABCDEFG').giveNext())
print(Give123s('12345').giveNext())
the output is: A 1 B 2 C 3
If I were more clever, I could figure out how to put the string literals inside the constructor...but that is not crucial right now.
My question is on the use of classes this way. Yes, an instance of the class gets created each time that that the call within the print() gets made. Yet the i's are 'permanent' in each class.
This strikes me as less of an object-oriented approach, and more of a way of using classes to accomplish encapsulation and/or a functional programming paradigm, since the instances are entirely transitory. In other words, an instance of the class is never instantiated for its own purposes; it is there only to allow access to the class-wide methods and variables within to do their thing, and then it is tossed away. In many cases, it seems like the class mechanism is used in a back-handed way, in order to leverage inheritance and name resolution/spacing: an instance of the class is never really required to be built or used, conceptually.
Is this standard Python form?
Bonus question: how would I put the string literals inside each class declaration? Right now, even if I change the _init__ for GiveABCs to
GiveNext.__init__(self, 'wxyz')
it completely ignores the 'wxyz' literal, and uses the 'ABCDEF' one - even though it is never mentioned...
Please don't learn Python with this code. As mentioned by others, this code goes against many Python principles.
One example: list is a Python builtin type. Don't overwrite it, especially not with a string instance!
The code also mixes class and instance variables and doesn't use super() in subclasses.
This code tries to simulate an iterator. So simply use an iterator:
give_abcs = iter('ABCDEFG')
give_123s = iter('12345')
for _ in range(3):
print(next(give_abcs))
print(next(give_123s))
# A
# 1
# B
# 2
# C
# 3
If you really want to fix the above code, you could use:
class GiveNext :
def __init__(self, iterable) :
self.i = - 1
self.iterable = iterable
def giveNext(self) :
self.i += 1
return self.iterable[self.i]
giveABCs = GiveNext('ABCDEFG')
give123s = GiveNext('12345')
for _ in range(3):
print(giveABCs.giveNext())
print(give123s.giveNext())
It outputs:
A
1
B
2
C
3
This code in the OP is an incredible amount of crap. Not only it is long, unreadable, misuses OO features, and does not use Python features at all (an iterator being a standard Python feature). Here is a suggestion for a more Pythonist approach:
giveABCs = iter('ABCDEFG')
give123s = iter('12345')
for i in range(3):
print(next(giveABCs))
print(next(give123s))
About your bonus question: I guess you are modifing the _init__() method of GiveABCs and Give123s. It is normal that whatever code you put in there has no effect, because the Python constructor is __init__() (with 2 leading underscores, not 1). So The constructor from GiveNext is not overloaded.
I would like to create temporary variables visible in a limited scope.
It seems likely to me that you can do this with a "with" statement, and I would think there is a construct that makes it easy to do, but I cannot seem to find it.
I would like something like the following (but it does not work this way of course):
pronunciation = "E_0 g z #_1 m p l"
# ...
with pronunciation.split() as phonemes:
if len(phonemes) > 2 or phonemes[0].startswith('E'):
condition = 1
elif len(phonemes) < 3 and phonemes[-1] == '9r':
condition = 2
So is there a simple way to make this work, using built-ins?
Thanks!
Python creates local variables with function scope (once a name is used it stays alive until the end of the function).
If you really want to limit scope then "del <var>" when you want it explicitly discarded, or create separate function to act as a container for a more limited scope.
You can create a method
def process_pronunciation(pronunciation):
phonemes = pronunciation.split()
if len(phonemes) > 2 or phonemes[0].startswith('E'):
condition = 1
elif len(phonemes) < 3 and phonemes[-1] == '9r':
condition = 2
return condition
When you call the method, the local variable phonemes won't be available in the global namespace.
pronunciation = "E_0 g z #_1 m p l"
condition = process_phonemes(pronunciation)
You could do it with with, but I don't think it's worth the trouble. Basically (in a python function) you have two scopes - global or local, that's it. If you want a symbol to have a lifespan shorter than the function you'll have to delete it afterwards using del. You could define your own context manager to make this happen:
class TempVar:
def __init__(self, loc, name, val):
self.loc = loc
self.name = name
self.val
def __enter__(self):
if self.name in self.loc:
self.old = self.loc[self.name]
self.loc[self.name] = self.val
def __exit__(self, *exc):
if hasattr(self, "old"):
self.loc[self.name] = self.old
else:
del self.loc[self.name]
then you can use it to get a temporary variable:
with TempVar(locals(), "tempVar", 42):
print(tempVar)
The working is that it modifies the dict containing local variables (which is supplied to the constructor via locals()) on entry and restoring it when leaving. Please note that this relies on that modifying the result returned by locals() actually modifies the local namespace - the specification does NOT guarantee this behaviour.
Another (and safer) alternative that was pointed out is that you could define a separate function which would have it's own scope. Remember it's perfectly legal to nest functions. For example:
def outer():
def inner(tempVar):
# here tempVar is in scope
print(tempVar)
inner(tempVar = 42)
# here tempVar is out of scope
with statement does not have its own scope , it uses the surrounding scope (like if the with statement is directly inside the script , and not within any function, it uses global namespace , if the with statement is used inside a function, it uses the function's namespace(scope)).
If you want the statements inside a with block to run in its own local scope, one possible way would be to move the logic to a function , that way the logic would be running in its own scope (and not the surrounding scope of with.
Example -
def function_for_with(f):
#Do something.
with pronunciation.split() as phonemes:
function_for_with(phonemes)
Please note, the above will not stop phonemes from being defined in the surrounding scope.
If you want that as well (move the phonemes into its own scope), you can move the complete with statement inside a function. Example -
def function_with(pronunciation):
with pronunciation.split() as phonemes:
#do stuff
pronunciation = "E_0 g z #_1 m p l"
function_with(pronunciation)
Expanding on #skyking's answer, here's an even more magical implementation of the same idea that reads almost exactly like you wrote. Introducing: the with var statement!1
class var:
def __init__(self, value):
import inspect
self.scope = inspect.currentframe().f_back.f_locals
self.old_vars = set(self.scope.keys())
self.value = value
def __enter__(self):
return self.value
def __exit__(self, type, value, traceback):
for name in set(self.scope.keys()) - self.old_vars:
del self.scope[name]
### Usage:
line = 'a b c'
with var (line.split()) as words:
# Prints "['a', 'b', 'c']"
print(words)
# Causes a NameError
print(words)
It does all the nasty extracting of local variables and names for you! How swell. If you space it quirkily like I did and hide the definition in a from boring_stuff import * statement, you can even pretend var is a keyword to all of your confused co-workers.
[1] If you actually use this, the ghost of a dead parrot will probably haunt you forever. The other answers provide much saner solutions; this one is more of a joke.
here is the code that I am having a problem with(simplified to make it clearer). It is for a text based game just to help learn things.
class Character(object):
def __init__(self):
self.level = 5
self.moveset = [None,None,None,None]
def movesetleveling(self):
if self.level > 4:
self.moveset[0] = Punch(self.level)
def punch(level):
damagedealt = random.randint(0,5)**level
return damagedealt
I would like to know how I can make self.moveset[0] = Punch() rather than being equal to the output of Punch() in this block of code. So that everytime i run it in a while loop it will re-evaluate the output of Punch() rather than evaluating Punch() once and assigning that to the 0th index of self.moveset[0].
You could assign a function to self.moveset[0] instead of its result (using functools.partial()):
from functools import partial
self.moveset[0] = partial(punch, self.level)
Then later, in your while loop, just call it:
while True:
new_punch_every_time = self.moveset[0]() #note: parentheses
self.moveset[0]() calls punch function with level parameter set to self.level (its value at the time of partial call).
It works because functions, methods are first class citizens in Python. You can pass them as parameters to other functions, return from functions, bind to a different name, append to a list, etc.
So i have a relatively convoluted setup for something I'm working on explained as follows:
This is is python. and more of a rough outline, but it covers everything I need. Though the process next function is the same so feel free to clean that up if you want.
#timer event that runs every .1 second and processes events in a queue
some_event_timer():
events.process_next()
class Event_queue:
def __init__(self):
self.events = []
def push(self, event, parameters):
self.events.insert(len(self.events), event, parameters)
def process_next(self):
event = self.pop(0)
event[0](event[1])
class Foo:
def __init__(self, start_value = 1):
self.value = start_value
def update_value(self, multiple):
self.value *= multiple
def return_bah(self)
return self.value + 3
class Bar:
def __init__(self, number1, number2):
self.init = number1
self.add = number2
def print_alt_value(self, in_value):
print in_value * (self.init + self.add)
That is a barebones of what I have, but it illustrates my problem:
Doing the below
events2 = Event_queue2()
foo1 = Foo(4) ----> foo1.value = 4 here
bar1 = Bar(4, 2)
events2.push(foo1.update_value,1.5)
events2.push(bar1.print_alt_value,foo1.value)
events2.push(bar.print_alt_value,foo1.return_bah())
events2.process_next() ----> should process update_value to change foo.value to 6
events2.process_next() ----> should process print_alt_value in bar class - expected 36
events2.process_next() ----> should process print_alt_value - expected 54
I initially expected my output to be 36 6 * (4 + 2)
I know why its not, foo1.value and foo1.return_bah() gets passed as an evaluated parameter (correct term?).
What I really want is to pass the reference to the variable or the reference to the method, rather than having it evaluate when I put it in my event queue.
Can anyone help me.
I tried searching, but I couldn't piece together what I wanted exactly.
TO get what I have now I initially looked at these threads:
Calling a function of a module from a string with the function's name in Python
Use a string to call function in Python
But I don't see how to support parameters from that properly or how to support passing another function or reference to a variable from those.
I suppose at least for the method call, I could perhaps pass the parameter as foo1.return.bah and evaluate in the process_next method, but I was hoping for a general way that would accept both standard variables and method calls, as the event_queue will take both.
Thank you for the help
Update edit:
So I following the suggestion below, and got really close, but:
Ok, so I followed your queue suggestion and got really close to what I want, but I don't completely understand the first part about multiple functions.
I want to be able to call a dictionary of objects with this as well.
for example:
names = ["test1", "test2"]
for name in names:
names_objs[name] = Foo(4)
Then when attempting to push via lambda
for name in names_list:
events2.push(lambda: names_objs[name].update_value(2))
doesn't work. When teh event actually gets processed it only runs on whatever name_objs[name] references, and if the name variable is no longer valid or has been modified outside the function, it is wrong.
This actually wasn't surprising, but adding a:
name_obj_hold = name_objs[name]
then pushing that didn't either. it again only operates on whatever name_obj_hold last referenced.
Can someone clarify the multiple funcs thing. I'm afraid I'm having trouble wrapping my head around it.
basically I need the initial method call evaluated, so something like:
names_objs[name].some_func(#something in here#)
gets the proper method and associated with the right class object instance, but the #something in here# doesn't get evaluated (whether it is a variable or another function) until it actually gets called from the event queue.
Instead of passing in the function to call func1 and the arguments that should be passed to the function, pass in a function func2 that calls func1 with the arguments that should be passed in.
d = {"a":1}
def p(val):
print val
def func1():
p(d["a"])
def call_it(func):
func()
call_it(func1)
d["a"] = 111
call_it(func1)
Within func1, d["a"] is not evaluated until func1 actually executes.
For your purposes, your queue would change to:
class EventQueue(object):
def __init__(self):
self.events = deque()
def push(self, callable):
self.events.append(callable)
def process_next(self):
self.events.popleft()()
collections.deque will be faster at popping from the front of the queue than a list.
And to use the EventQueue, you can use lambdas for quick anonymous function.
events2 = EventQueue()
foo1 = Foo(4)
bar1 = Bar(4, 2)
events2.push(lambda: foo1.update_value(1.5))
events2.push(lambda: bar1.print_alt_value(foo1.value))
events2.push(lambda: bar1.print_alt_value(foo1.return_bah()))
events2.process_next()
events2.process_next() # 36.0
events2.process_next() # 54.0
For Edit:
In this case you need to "capture" the value in a variable that is more tightly scoped than the loop. You can use a normal function and partial() to achieve this.
for name in names_list:
def update(name):
names_objs[name].update_value(2)
events2.push(partial(update, name))