Does Python have something like Perl 5.10's "state" variables? - python

In Perl 5.10, I can say:
sub foo () {
state $x = 1;
say $x++;
}
foo();
foo();
foo();
...and it will print out:
1
2
3
Does Python have something like this?

A class may be a better fit here (and is usually a better fit for anything involving "state"):
class Stateful(object):
def __init__(self):
self.state_var = 0
def __call__(self):
self.state_var = self.state_var + 1
print self.state_var
foo = Stateful()
foo()
foo()

The closest parallel is probably to attach values to the function itself.
def foo():
foo.bar = foo.bar + 1
foo.bar = 0
foo()
foo()
foo()
print foo.bar # prints 3

Python has generators which do something similar:
What does the "yield" keyword do in Python?

Not sure if this is what you're looking for, but python has generator functions that don't return a value per se, but a generator object that generates a new value everytime
def gen():
x = 10
while True:
yield x
x += 1
usage:
>>> a = gen()
>>> a.next()
10
>>> a.next()
11
>>> a.next()
12
>>> a.next()
13
>>>
look here for more explanation on yield:
What does the "yield" keyword do in Python?

Here's one way to implement a closure in python:
def outer():
a = [4]
def inner():
print a[0]
a[0] = a[0] + 1
return inner
fn = outer()
fn() # => 4
fn() # => 5
fn() # => 6
I borrowed this example verbatim from a python mailing list post.

Not that I'm recommending this, but just for fun:
def foo(var=[1]):
print var[0]
var[0] += 1
This works because of the way mutable default arguments work in Python.

Yes, though you have to declare your global variable first before it is encountered in foo:
x = 0
def foo():
global x
x += 1
print x
foo()
foo()
foo()
EDIT: In response to the comment, it's true that python has no static variables scoped within a function. Note that x in this example is only exposed as global to the rest of the module. For example, say the code above is in test.py. Now suppose you write the following module:
from test import foo
x = 100
foo()
foo()
The output will be only 1 and 2, not 101 and 102.

You could also use something like
def static_num2():
k = 0
while True:
k += 1
yield k
static = static_num2().next
for i in range(0,10) :
print static()
to avoid a global var. Lifted from this link about the same question.

The preferable way is to use class or generator (yield).
For the sake of completeness here's a variant w/ closure in Python 3.x:
>>> def make_foo():
... x = 1
... def foo():
... nonlocal x
... print(x)
... x += 1
... return foo
...
>>> foo = make_foo()
>>> foo()
1
>>> foo()
2
>>> foo()
3

>>> def foo():
x = 1
while True:
yield x
x += 1
>>> z = iter(foo())
>>> next(z)
1
>>> next(z)
2
>>> next(z)
3

Here's another dirty cheap way to do it, it's a variation on Tryiptich's answer, but using decorators
def static_var( name, value ):
def dec( function ):
setattr( function, name, value )
return function
return dec
#static_var( 'counter', 0 )
def counting_function():
counting_function.counter = counting_function.counter + 1
print counting_function.counter
"""
>>> counting_function()
1
>>> counting_function()
2
>>> counting_function()
3
>>> counting_function()
4
>>> counting_function()
5
>>>
"""

Related

Is it possible to set an instance variable for a function in python?

Lets say I have a function with a variable duration inside it.
Is there any way to set the Duration` value outside of the function in some other nonparent function without adjusting any parameter?
With Python 3.x you can declare it with the nonlocal keyword
def make_slow(action):
slowDuration = None
def slowAction(self, colony):
nonlocal slowDuration
slowDuration = 10 # It is changing the variable from the scope above
If you want to change a value from somewhere else and you mustn't return the value, try going global... Note this may pollute your current namespace.
For a more pythonic approach, you should use something like self.slowDuration. That's what objects are for.
slowDuration is a local variable of the slowAction function. The point of local variables is that they are only accessible inside the function.
You should change the slowAction function so it uses a slowDuration variable that is defined somewhere else, for example as a member variable of the class that make_slow apparently belongs to.
You can also make slowAction be an instance of a class that overrides the __call__ method.
>>> class Counter:
... def __init__(self):
... self.count = 0
... def __call__(self, delta):
... self.count += delta
... print(self.count)
... def set_count(self, c):
... self.count = c
...
>>> c = Counter()
>>> c(1)
1
>>> c(3)
4
>>> c(3)
7
>>> c(3)
10
>>> c.set_count(42)
>>> c(-2)
40
You could also use some trickery to make the shared variable available on the function object itself:
def makeCounter():
counter = None
def counter_func():
counter.count += 1
print(counter.count)
counter = counter_func
counter.count = 0
return counter
and use it like this:
>>> c = makeCounter()
>>> c()
1
>>> c()
2
>>> c()
3
>>> c()
4
>>> c.count = 42
>>> c()
43
>>> c()
44
>>> c()
45
But in general, "clever" code like that should be avoided unless you have a very good reason to use it, because it makes the code base harder to understand.

Python scope: how to reach local variable? [duplicate]

It's ok to get and print the outer function variable a
def outer():
a = 1
def inner():
print a
It's also ok to get the outer function array a and append something
def outer():
a = []
def inner():
a.append(1)
print a
However, it caused some trouble when I tried to increase the integer:
def outer():
a = 1
def inner():
a += 1 #or a = a + 1
print a
>> UnboundLocalError: local variable 'a' referenced before assignment
Why does this happen and how can I achieve my goal (increase the integer)?
In Python 3 you can do this with the nonlocal keyword. Do nonlocal a at the beginning of inner to mark a as nonlocal.
In Python 2 it is not possible.
Workaround for Python 2:
def outer():
a = [1]
def inner():
a[0] += 1
print a[0]
A generally cleaner way to do this would be:
def outer():
a = 1
def inner(b):
b += 1
return b
a = inner(a)
Python allows a lot, but non-local variables can be generally considered as "dirty" (without going into details here).

Pythonic solution for conditional arguments passing

I have a function with two optional parameters:
def func(a=0, b=10):
return a+b
Somewhere else in my code I am doing some conditional argument passing like:
if a and b:
return func(a, b)
elif a:
return func(a)
elif b:
return func(b=b)
else:
return func()
Is there anyway to simplify code in this pattern?
EDIT:
Let's assume that I'm not allowed to implement default parameter logic inside func.
I may have several functions like func: func1, func2 and func3 would all contain the
a = a or 0
b = b or 10
statements.
But I'm invoking these series of functions to eliminate duplication. (using a decorator)
If you don't want to change anything in func then the sensible option would be passing a dict of arguments to the function:
>>> def func(a=0,b=10):
... return a+b
...
>>> args = {'a':15,'b':15}
>>> func(**args)
30
>>> args={'a':15}
>>> func(**args)
25
>>> args={'b':6}
>>> func(**args)
6
>>> args = {}
>>> func(**args)
10
or just:
>>>func(**{'a':7})
17
You can add a decorator that would eliminate None arguments:
def skip_nones(fun):
def _(*args, **kwargs):
for a, v in zip(fun.__code__.co_varnames, args):
if v is not None:
kwargs[a] = v
return fun(**kwargs)
return _
#skip_nones
def func(a=10, b=20):
print a, b
func(None, None) # 10 20
func(11, None) # 11 20
func(None, 33) # 10 33
Going by the now-deleted comments to the question that the check is meant to be for the variables being None rather than being falsey, change func so that it handles the arguments being None:
def func(a=None, b=None):
if a is None:
a = 0
if b is None:
b = 10
And then just call func(a, b) every time.
to solve your specific question I would do:
args = {'a' : a, 'b' : b}
for varName, varVal in args.items():
if not varVal:
del args[varName]
f(**args)
But the most pythonic way would be to use None as the default value in your function:
f(a=None, b=None):
a = 10 if a is None else a
...
and just call f(a, b)
By default, all methods in Python take variable arguments.
When you define an argument in the signature of the method, you explicity make it required. In your snippet, what you are doing is giving it a default value - not making them optional.
Consider the following:
>>> def func(a,b):
... a = a if a else 0
... b = b if b else 10
... return a+b
...
>>> a = b = None
>>> func(a,b)
10
>>> a = 5
>>> b = 2
>>> func(a,b)
7
>>> func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 2 arguments (0 given)
In this snippet, both a and b are required, since I didn't define any default values. They are not optional.
However in your snippet, because you have given them defaults in the method signature they look like they are optional, but in fact they just have defaults assigned to them.
>>> def func(a=0,b=10):
... return a+b
...
>>> func()
10
You can confirm this by checking the argument list inside the body of the method:
>>> def func(a=b,b=10):
... print locals().keys()
...
>>> func()
['a', 'b']
One way to have your function accept any number of arguments (in other words, making them all optional):
>>> def func(*args,**kwargs):
... print len(args),args
... print len(kwargs),kwargs
...
>>> func("hello","world",a=5,b=10)
2 ('hello', 'world')
2 {'a': 5, 'b': 10}
>>> func()
0 ()
0 {}
>>> func(1,2)
2 (1, 2)
0 {}
>>> func(a)
1 (5,)
0 {}
>>> func(foo="hello")
0 ()
1 {'foo': 'hello'}
Why not pass that logic to the function?
def func(a, b):
a = a or 0
b = b or 10
return a + b
To answer your literal question:
func(a or 0, b or 10)
Edit:
See comment why this doesn't answer the question.
You can use the ternary if-then operator to pass conditional arguements into functions
https://www.geeksforgeeks.org/ternary-operator-in-python/
For example, if you want to check if a key actually exists before passing it you can do something like:
def func(arg):
print(arg)
kwargs = {'name':'Adam')
func( kwargs['name'] if 'name' in kwargs.keys() else '' ) #Expected Output: 'Adam'
func( kwargs['notakey'] if 'notakey' in kwargs.keys() else '' ) #Expected Output: ''
This might work:
def f(**kwargs):
a = get(kwargs, 0)
b = get(kwargs, 10)
return a + b

Is there a Pythonic way to close over a loop variable?

I just ran across Eric Lippert's Closing over the loop variable considered harmful via SO, and, after experimenting, realized that the same problem exists (and is even harder to get around) in Python.
>>> l = []
>>> for r in range(10):
... def foo():
... return r
... l.append(foo)
...
>>> for f in l:
... f()
...
9
9
9
# etc
and, the standard C# workaround doesn't work (I assume because of the nature of references in Python)
>>> l = []
>>> for r in range(10):
... r2 = r
... def foo():
... return r2
... l.append(foo)
...
>>> for f in l:
... f()
...
9
9
9
# etc
I recognize that this isn't much of a problem in Python with its general emphasis on non-closure object structures, but I'm curious if there is an obvious Pythonic way to handle this, or do we have to go the JS route of nested function calls to create actually new vars?
>>> l = []
>>> for r in range(10):
... l.append((lambda x: lambda: x)(r))
...
>>> for f in l:
... f()
...
0
1
2
# etc
One way is to use a parameter with default value:
l = []
for r in range(10):
def foo(r = r):
return r
l.append(foo)
for f in l:
print(f())
yields
0
1
2
3
4
5
6
7
8
9
This works because it defines an r in foo's local scope, and binds the default value to it at the time foo is defined.
Another way is to use a function factory:
l = []
for r in range(10):
def make_foo(r):
def foo():
return r
return foo
l.append(make_foo(r))
for f in l:
print(f())
This works because it defines r in make_foo's local scope, and binds a value to it when make_foo(r) is called. Later, when f() is called, r is looked-up using the LEGB rule. Although r is not found in the local scope of foo, it is found in the enclosing scope of make_foo.

Why doesn't a sub-function inherit scope in Python?

I don't understand why the following doesn't work:
def foo( x ):
n = 1
summe = 0
def bar():
n -= 1
for i in range(0,10):
y = x+i+n
x += i
summe += y
print "{0} = {1} + {2} + {3}".format(y,x,i,n)
bar()
print "summe =", summe
return summe
Why is it that bar() doesn't inherit the scope of foo()? Is this a C'ism that I need to forget? Is there a way I can make that work?
PEP 3104 provides an explanation and a solution for this problem. The issue is Python treats any assignment to a name as a local variable declaration.
>>> n = 1
>>> def bar():
>>> n = n + 1
>>>
>>> bar()
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
bar()
File "<pyshell#7>", line 2, in bar
n = n + 1
UnboundLocalError: local variable 'n' referenced before assignment
There is a few workarounds for this problem if you use a Python version without the nonlocal keyword. One ugly trick is to wrap your variable in a list:
>>> n=[1]
>>> def bar():
>>> n[0] = n[0] + 1
>>>
>>> bar()
>>> n
[2]
Although this trick works, it is usually better to rewrite the code to remove the need for non-local assignments.
I actually found this question while searching for a solution to a slightly different problem. Local variables aren't inherited by sub's inherited but there's nothing stopping you from passing the variable to the inner function and then assigning the results on return.
This is in addition to the nonlocal statement in PEP 3104. It's slightly less ugly and makes memorizing yet another python keyword less crucial.
def foo( x ):
n = 1
summe = 0
def bar(n):
n -= 1
return n
for i in range(0,10):
n = bar(n)
y = x+i+n
x += i
summe += y
print "{0} = {1} + {2} + {3}".format(y,x,i,n)
print "summe =", summe
return summe

Categories