Can you have member variables in a python function - python

I'm wondering if there's a way to have member variables in a function instance. For example,
incr_instance = incrementor(1)
incr_instance()
incr_instance()
incr_instance()
would print out
1
2
3
Yes, I know I can just use a class, but this is a question about weird language quirks in the python language.

You can have a function incrementor that returns another function that keeps incrementing the counter n (the inner function uses nonlocal n to access n instead of using incrementor.n):
def incrementor(n):
def inc():
nonlocal n
n += 1
return n - 1
return inc
Test:
incr_instance = incrementor(1)
incr_instance_2 = incrementor(10)
print(incr_instance())
print(incr_instance())
print(incr_instance())
print(incr_instance_2())
print(incr_instance_2())
print(incr_instance_2())
print(incr_instance())
print(incr_instance())
print(incr_instance())
print(incr_instance_2())
print(incr_instance_2())
print(incr_instance_2())
Output:
1
2
3
10
11
12
4
5
6
13
14
15

You can do the following:
def incrementor(n):
def incrementor_aux():
incrementor_aux.x += n
print(incrementor_aux.x)
incrementor_aux.x = 0
return incrementor_aux
You can even create multiple incrementors and have them increment their values independantly:
incr_instance1 = incrementor(1)
incr_instance2 = incrementor(1)
incr_instance1() #prints 1
incr_instance1() #prints 2
incr_instance2() #prints 1
incr_instance1() #prints 3
incr_instance2() #prints 2
What happens is a new instance of incrementor_aux function is created everytime you call incrementor, with its own x local value which stays in each instance's scope.

Here's one that doesn't use function attributes:
from functools import partial
def increment(x):
def gen():
y = x
while True:
y += 1
yield y
g = gen()
return partial(next, g)
f1 = increment(1)
f1()
# 2
f1()
# 3
f2 = increment(0)
f2()
# 1
f2()
# 2
f1()
# 4

def incrementor(i=0):
def inc():
inc.i += 1
return inc.i
inc.i = i - 1
return inc
incr_inst = incrementor(1)
incr_inst() # 1
incr_inst() # 2
But for this actual task
from itertools import count
incr = count(1)
incr() # 1
incr() # 2

Looks like you want a factory that gives you functions with local state, without using a class. Instead of making the state variable a member of the function, I'd use python's nonlocal syntax:
def incrementor(start):
state = start -1
def interior():
nonlocal state
state += 1
return state
return interior
This will print 7, then 8:
inc7 = incrementor(7)
print(inc7())
print(inc7())
This will print 1, then 9 10, then 2 3; each of the functions inc1() and inc7() is going at its own pace.
inc1 = incrementor(1)
print(inc1())
# Yes, I can mix them
print("inc7 again:", inc7(), inc7())
print("inc1 again:", inc1(), inc1())

I fancied having a go at this and tried to make it concise, here's what I ended up with. It's not best practice but you asked for quirky!
The aim here was to not pass the value back into the function, as the OP requested.
index = 0;
incr_instance = lambda: [index, exec("global index; index += 1")]
print(incr_instance()[0])
print(incr_instance()[0])
print(incr_instance()[0])
Will print
0
1
2
Index is our global which keeps track of our progress, the lambda expression executes two statements. The first statement returns the value of index and the second increments it, which had to be done using exec as you cannot assign variables from a lambda as it's for expressions only. The two statements are within square brackets in order to run both; otherwise it will return index only.
The [0] index gives us the value of index rather than the result of the exec expression (always None)

Related

I think it's a basic question but I don't get why I get 0 in this function

a = 0
def add(number):
number += 1
return number
for i in range(20):
add(a)
print(a)
I'm wondering why I get 0 for the print(a) call in the last line.
I put 0 for the first loop of the add()function and it should return 1 and so on in the for loop.
What am I missing?
I think you're just missing assigning the return value of add() back to a. Without the assignment, a never changes because function arguments are passed by value, not reference.
a = 0
def add(number):
number += 1
return number
for i in range(20):
a = add(a)
print(a)
it seems you aren't actually changing the value of a, try changing your for loop to look something like this:
for i in range(20):
a = add(a)
To make use of the return keyword, make sure you understand that it sends back a value to the main program, treat it like another variable.
When you pass an int to a function, you're basically passing a copy of it. So your function adds 1 to a copy of a and returns it, but there's nothing there to catch the returned value.
When you pass a list or dict to a function, you're passing a reference to the original object, though. If you change it inside your function it changes thoughout your code:
a = 0
b = [0]
c = {"engelbert": 0}
def add(number, lst, dct):
number += 1
lst[0] += 1
dct["engelbert"] += 1
# No return statement:
# Function returns `None` by default
for i in range(20):
add(a, b, c)
print(a, b, c)
# 0 [20] {'engelbert': 20}

Create a function that will increment by one when called

I am needing a function that will increment by one every time it is called. I have used count but every time I do it resets the count back to the original value plus one count. I have seen lots of code but none of it works. Here is what I have now
I have done lots of looking into loops and iterations
def count_row():
count = 1
while count >= 1:
yield count
count += 1
return count
You can use itertools.count.
from itertools import count
counter = count(1)
next(counter) # 1
next(counter) # 2
Stateful function
If you absolutely want a stateful function instead of calling next, you can wrap the count in a function.
def counter(_count=count(1)):
return next(_count)
counter() # 1
counter() # 2
Class
Alternatively, itertools.count being a class, you can inherit from it to extend it's behaviour and make it a callable.
class CallableCount(count):
def __call__(self):
return next(self)
counter = CallableCount(1)
counter() # 1
counter() # 2
Using a class would be my preferred approach since it allows instantiating multiple counters.
You need a closure. Define a function make_counter which initializes a local variable, then defines and returns a function that increments that variable on each call.
def make_counter():
count = -1
def _():
count += 1
return count
return _
count_row = make_counter()
Now count_row will return a new value on each call:
>>> count_row()
0
>>> count_row()
1
This is sort of the dual of a class. You have a function that "wraps" some data (closes over the variable), instead of a piece of data with an associated method. The class version; note the similarity to make_counter:
class Count:
def __init__(self):
self.count = -1
def __call__(self):
self.count += 1
return count
An instance of this class now behaves like our previous closure.
>>> count_row = Count()
>>> count_row()
0
>>> count_row()
1
You can use a generator here that increments value by one every time it's called using next():
def count_row():
count = 0
while True:
count += 1
yield count
itr = count_row()
print(next(itr)) # 1
print(next(itr)) # 2
If you look closely, this is equivalent to what itertools.count() does.
if I got this right this should work:
count=0
def count_row(count):
count += 1
return count

Assign global variable to a function's local variable

I want to loop over some global variable i and each time compile a new function that uses the instantaneous value of i. I can then use each of these functions in future, independent of the current value of i.
The problem is I can't find a good way to make the global variable i stay local within the namespace of the function. For example:
i = 0
def f():
return i
i = 1
print(f()) #Prints 1, when I want 0
I found one solution but it seems very clumsy:
i = 0
def g(x):
def h():
return x
return h
f = g(i)
i = 1
print(f()) #Prints 0 as I wanted.
Is there a better way to do this, maybe some kind of decorator?
Python functions are objects, you can set an attribute on them:
i = 0
def f():
return f.i
f.i = i
i = 1
print(f()) # Prints 0 as you wanted
or:
for i in range(5):
f.i = i
print(f())
Note that an argument would do the same.

Calculations - Using functions

I am a bit confused over this.
I have a function. Inside that function it asks a number of questions, one is the following based on the number of gardens they have - so if they say they have 2 gardens it will ask this question twice and should add the 100 twice to the calculation:
gardens = int(input("How many gardens do you have: "))
def gard():
calc = 0
gardener = input("Do you need a gardener? Y or N ")
if gardener == "Y" or gardener == "y":
calc = calc + 100
else:
calc = calc + 0
for i in range(gardens):
gard()
How do I keep a running total outside of the function? When I put print(calc) inside the function it just displays 100 each time they say Y but doesn't add it together.
Edited to include updated code:
The eMake section (the IF statement) returns a value - but it only ever returns the first in the calculation at the end?
Also struggling to do the area section since there are numerous ws. It only stores the last value for the variable.
noGard = int(input("Enter number of gards which require cleaning: "))
#Defining variables
Calc = 0
Area = 0
emCalc = 0
#Room information
def GInfo():
global Calc
global Area
gName = input("Enter gard name: ")
noW = int(input("How many w are in the "+gName + "? "))
#Repeats the questions for each W
for i in range(noW):
Height = float(input("What is the w height of in metres? "))
Width = float(input("What is the w width in metres? "))
Area = Height * Width
#Asks if w needs to be removed
w = input("Does w need removing? Y or N ")
if w == "Y" or w == "y":
Calc = Calc + 70
else:
Calc = Calc + 0
print (" ")
#Returns the values
return Calc
return Area
#Calculate Sarea
#Identifying e
def e():
global emCalc
#eMake
eMake = input("What make of e - HH or NN? ")
if eMake == "HH" or "hh":
emCalc = emCalc + 200
elif eType == "NN" or "nn":
emCalc = emCalc + 50
else: print("You have entered an invalid e make")
#Returns the values
return emCalc
#Repeats the g information questions for each g
for i in range(noGard):
GInfo()
# Runs the E function
e()
#Print total without VAT
total = Calc + emCalc
print(total)
print(Area)
Your function should return the calculated value.
def gard():
...
return calc
total = 0
for _ in range(gardens):
total += gard()
print 'Total: ', total
The whole point of functions, really, is that they take parameters and return values. (Some languages, although not Python, refer to functions that don't do this as "procedures".)
That is what you need to do here: your gard function needs to return the value of calc. You probably don't want to actually do the addition inside the function itself, but if you did, you would also need to accept the current value of calc as a parameter, which you would pass in from your for loop.
Functions, in the strictest sense, do not have state. When writing functional programs, one typically aims to keep their functions pure, meaning that the result of the function does not depend on anything but its inputs and does not cause observable side effects.
But Python is not a purely functional language. It is an object-oriented procedural language which models functions as objects, and objects can be stateful. So you can do what you're aiming to, if you don't take the word "function" too literally.
The Right Thing™
Create a class which models your data and the operations on it:
>>> class F(object):
... def __init__(self):
... self.x = 0
... def f(self):
... self.x += 1
... return self.x
...
>>> my_f = F()
>>> my_f.f()
1
>>> my_f.f()
2
Fun and naughty ways
Add state to the function object, taking advantage of the fact that function bodies aren't executed until the function is called:
>>> def f():
... f.x += 1
... return f.x
...
>>> f.x = 0
>>> f()
1
>>> f()
2
If you want to do this transparently (that is, make it so that you don't have to add this state to the function right after defining it) you can close over the state by having a function create a function:
>>> def g():
... def func():
... func.x += 1
... return func.x
... func.x = 0
... return func
...
>>> f = g()
>>> f()
1
>>> f()
2
To take it a step further, create a decorator so you don't have to do any assignments after defining the function at all:
>>> def with_x(func):
... func.x = 0
... return func
...
>>> #with_x
... def f():
... f.x += 1
... return f.x
...
>>> f()
1
>>> f()
2
Or you can just use global to let a function refer to something outside of its local scope, not taking advantage of the fact that functions are objects:
>>> x = 0
>>> def f():
... global x
... x += 1
... return x
...
>>> f()
1
>>> f()
2
>>> x
2
Updates for your edit
Since you went with global I'll first refer you to a good question that explains global. Using global variables in a function other than the one that created them
Now, as for your particular problems:
The eMake section (the IF statement) returns a value - but it only ever returns the first in the calculation at the end?
Sure, there are a couple problems here and one of them is definitely a common one for beginners. or takes higher precedence than == so your condition parses like this:
if (eMake == "HH") or ("hh"):
This gets people all the time. In Python, if a value isn't a boolean and you put it in a conditional statement, it gets evaluated as a boolean using a series of truthiness rules. In this case a non-empty string is considered True so you are basically saying if (eMake == "HH") or True.
To fix this, fix the right-hand side of the condition:
if (eMake == "HH") or (eMake == "hh"):
By the way, you probably meant elif (eMake == "NN") or (eMake == "nn"): instead of elif eType == "NN" or "nn": because you never defined eType (and for the reason above, too.) If you type nn there you'll get an exception.
Also struggling to do the area section since there are numerous ws. It only stores the last value for the variable.
This is because you repeatedly assign to the same variable with Area = Height * Width. Since Area is global, it's the same variable every time you call GInfo(). If it wasn't global it would be a new variable every time you called the function, but then you would need to return it and assign the return value to a variable in order to save the value. Otherwise it would disappear since it was never assigned to anything.
Now, I don't know what you are trying to do with the areas you're calculating. Do you want to keep them separate or sum them together?
If you want to keep them separate, you'll need to use a data structure. In this case, you'd definitely want to use a list. Using the append() method of lists, you can add an item to the list. So it would look something like this:
areas = [] # empty list
def GInfo():
global areas
# the stuff before the loop
for i in range(noW):
Height = float(input("What is the w height of in metres? "))
Width = float(input("What is the w width in metres? "))
areas.append(Height * Width)
# the stuff after the loop
If you want to sum them together, just make sure you add each individual area calculation to the previous result, just like you did with Calc:
Area += Height * Width
One more thing: your GInfo() function is only returning Calc and not Area as well. Functions can only return one value. In the mathematical sense, a function is a many-to-one mapping between two sets. So in Python, a function ends at the return statement. Nothing else gets executed after that.
In order to get both the value of Calc as well as the value of Area from the return value of GInfo(), you will have to return a data structure. Usually this would be a tuple.
return (Calc, Area)
But your code doesn't assign the return value of GInfo() to anything. Instead, it uses the global declaration to change the value of the global variables. So there shouldn't be an issue here.

Looping structure best approach

Consider these two variants of the same loop structure:
x = find_number_of_iterations()
for n in range(x):
# do something in loop
and:
for n in range(find_number_of_iterations()):
# do something
Will the second loop evaluate the method find_number_of_iterations in every subsequent loop run, or will the method find_number_of_iterations be evaluated only once even in the second variant?
I suspect that your mentor's confusion is traceable to the fact that the semantics of Python's for loop is so much different than in other languages.
In a language like C a for loop is more or less syntactic sugar for a while loop:
for(i = 0; i < n; i++)
{
//do stuff
}
is equivalent to:
i = 0;
while(i < n)
{
//do stuff
i++
}
In Python it is different. Its for loops are iterator-based. The iterator object is initialized just once and then consumed in subsequent iterations. The following snippets show that Python's for loop is not (easily) translatable into a while loop, and also shows that with a while loop your mentor's concern is valid:
>>> def find_number_of_iterations():
print("called")
return 3
>>> for i in range(find_number_of_iterations()): print(i)
called
0
1
2
>>> i = 0
>>> while i < find_number_of_iterations():
print(i)
i += 1
called
0
called
1
called
2
called
The function is called once. Logically, were it to be called on every iteration then the loop range could change causing all kinds of havoc. This is easily tested:
def find_iterations():
print "find_iterations called"
return 5
for n in range(find_iterations()):
print n
Results in:
$ python test.py
find_iterations called
0
1
2
3
4
Either way, the function only gets called once. You can demonstrate this as follows:
>>> def test_func():
"""Function to count calls and return integers."""
test_func.called += 1
return 3
# first version
>>> test_func.called = 0
>>> x = test_func()
>>> for _ in range(x):
print 'loop'
loop
loop
loop
>>> test_func.called
1
# second version
>>> test_func.called = 0
>>>
>>> for _ in range(test_func()):
print 'loop'
loop
loop
loop
>>> test_func.called
1
The function is called once, and the result of calling that function is passed to range (then the result of calling range is iterated over); the two versions are logically equivalent.

Categories