Can global variable be passed into Python function?

M

Mark H. Harris

I think you're talking nonsense. I've been happily using dict dispatch
tables for years and, like Steven and presumably many others, find them
dead easy to read. Perhaps you should invest in a better optician
and/or glasses?

That's the rub right there... yes, they are easy to read for someone who uses them for many years... but not to the average coder, or scientist who just happens to be using python (like she may have used BASIC in the past, or someone else who has no idea at this stage of their python career what adict is, let alone what a dict dispatch table is (I like to think of virtual function table myself, which its not) ... that is really the point.

Readability is important when the reader needs eye glasses... that is thewhole point in a nutshell.
 
C

Chris Angelico

Your example:

compare_key = {
# Same target(s).
ast.Assign: lambda node: ' '.join(dump(t) for t in node.targets),
# Same target and same operator.
ast.AugAssign: lambda node: dump(node.target) + dump(node.op) + "=",
# A return statement is always compatible with another.
ast.Return: lambda node: "(easy)",
# Calling these never compatible is wrong. Calling them
# always compatible will give lots of false positives.
ast.Expr: lambda node: "(maybe)",
# These ones are never compatible, so return some
# object that's never equal to anything.
ast.Import: lambda node: float("nan"),
ast.ImportFrom: lambda node: float("nan"),
ast.Pass: lambda node: float("nan"),
ast.Raise: lambda node: float("nan"),
ast.If: lambda node: float("nan"),
}

vs (my proposal):

with key from ast:
if Assign:
return ' '.join(dump(t) for t in node.targets)
elif AugAssign:
# Same target and same operator.
return dump(node.target) + dump(node.op) + "="
elif Return:
# A return statement is always compatible with another.
return "(easy)"
elif Expr:
# Calling these never compatible is wrong. Calling them
# always compatible will give lots of false positives.
return "(maybe)"
else:
# These ones are never compatible, so return some
# object that's never equal to anything.
return float("nan")

Which do *you* find more readable?

Your proposal requires that I wrap the whole thing up in a function,
or else pass the lookup key to my function every time and switch on
it, instead of getting back a single function once. So you haven't
truly matched the functionality, which means it's hard to compare
readability - I'd have to also look at how readable the call site is,
and that's definitely going to be penalized. Remember, I pointed out
that the function gets called more than once - once on the special
case and then once in a loop.

Plus, how do you implement mutability? The else clause needs to do a
one-off print to stderr, so either the dispatch table needs to be
changed, or the else clause needs some other system of keeping track
of its previously-seen list. Additionally, I have another script that
actually monkey-patches the Expr case out, simply by changing the
dict. It works just fine, because, again, the dict is mutable. To do
that with a switch statement, I'd need to put an explicit 'if' into
there. Show me that version and let's see how readable it is.

There may be a case (pun intended) for adding a switch block to
Python, but this isn't a strong supporting instance.

ChrisA
 
S

Steven D'Aprano

No, I mean this:

class StateMachine:
def __init__(self):
sm = self

class Idle:
def connect(self):
...
sm.set_state(Connecting)
...
[...]


Ah, interesting. You're not just using the classes as symbols, you
actually are giving them behaviour and using a State pattern.

But I'm not sure that there is a good reason to put the class definitions
inside the __init__ method. That means every time you create a new
StateMachine instance, the classes have to be re-created. (That might be
a feature if you intend for instance1.IDLE != instance2.IDLE, but I don't
see any good reason for that.)

It will be much more efficient if you pull all the Idle, etc. classes out
and make them top-level global classes. Or at least stick them inside the
StateMachine class, which means that they only get created once, and are
shared between all StateMachine instances. But then the closure over sm
won't work... I'll need to think some more about that.

However, the example as given won't quite work. You never instantiate the
Idle etc. classes, which means the methods won't work. You need to make
them class methods or static methods, or perform some metaclass magic
prevent them from being turned into instance methods when called.

Since in this example you've actually got significant behaviour in the
states, they aren't just symbols, and some sort of solution along these
lines (whether you use multiple inner classes or not) is appropriate.
 
C

Chris Angelico

A colleague of mine taught me decades back that the whole point of OO
was the avoidance of if and switch statements. So if your code has an if
or switch statement, chances are you are doing something wrong.

I agree.

*boggle*

Are you seriously suggesting that every condition requires the
declaration of an object type? UGH! No thank you.

ChrisA
 
C

Chris Angelico

Forgive me. I would rewrite the structure,

switch x:
case GT_1:
__atan__Gt_1__(x)
case LT_1:
__atan__Lt_1__(x)
case IS_1:
a = gpi/4
case IS_n1:
a = -gpi/4
default:
__atan__(x)

or somesuch... way better... yes, there are some issues, but all workable with some effort

Does your switch construct have to handle the magic of GT_1 meaning ">
1", or do you first figure out where it falls with an if/elif tree?

ChrisA
 
C

Chris Angelico

However, the example as given won't quite work. You never instantiate the
Idle etc. classes, which means the methods won't work. You need to make
them class methods or static methods, or perform some metaclass magic
prevent them from being turned into instance methods when called.

Actually, he does - setting a state means instantiating that state. I
still don't see where the benefit is, beyond that you reach the right
margin quicker than anyone else does.

ChrisA
 
N

Ned Batchelder

Forgive me. I would rewrite the structure,

switch x:
case GT_1:
__atan__Gt_1__(x)
case LT_1:
__atan__Lt_1__(x)
case IS_1:
a = gpi/4
case IS_n1:
a = -gpi/4
default:
__atan__(x)

or somesuch... way better... yes, there are some issues, but all workable with some effort

Mark, if you are going to advocate for a feature, find a good use case,
this one is absurd. Where did the constants GT_1 etc, come from?
 
G

Gregory Ewing

Ben said:
That whole chapter of the tutorial talks about “variable” and “variable
assignment”, when IMO it should avoid those terms and use the clearer
terminology of “reference”, “name”, and “binding a name to a value”.

What about all the other things that can be assigned
to but aren't names? (List and dict elements, attributes,
etc.) What do you suggest calling them?
 
G

Gregory Ewing

Mark said:
if (n**2 < D(1)):
a = __atan__(n)
elif (n == D(1)):
a = gpi/4
elif (n == D(-1)):
a = -(gpi/4)
elif (n < D(-1)):
a = __atan__Lt_neg1__(n)
else:
a = __atan__Gt_1__(n)

That's not a candidate for a switch statement, because
the comparisons are not all equality comparisons.
 
R

Roy Smith

A colleague of mine taught me decades back that the whole point of OO
was the avoidance of if and switch statements. So if your code has an if
or switch statement, chances are you are doing something wrong.

This sounds like a classic case of a useful observation being taken out
of context.

On of the standard mantras about OOP is that you should not "use code to
find code". By that, they mean, you should not do things like
(pseudo-code):

if type(obj) == Foo:
frobnicate_with_foo(obj)
else if type(obj_ == Bar:
frobnicate_with_bar(obj)
else:
frobnicate_with_other(obj)

But rather, you should let the class dispatch machinery handle figuring
out which version of frobnicate() to call. That's a reasonable
statement, but somehow that seems to have gotten twisted into, "If
you're doing OOP, you should never have any 'if' statements".
 
M

Mark H. Harris

Mark, if you are going to advocate for a feature, find a good use case,
this one is absurd. Where did the constants GT_1 etc, come from?

Not at all. Think of the C switch block... if you read about it in the K & R you'll think its useless... because it can only switch on a SINGLE character. So, to make it work what do you do??? You create some constants that are actually really and totally just a number... one character... that represents GT_1 or anything else.

The switch block "switches" on a character in C. In python it could switch on absolutely anything we like... the point is not how it switches... buthow it "looks" to human beings, and "maybe" how it performs.

For me its more important that code be readable first and foremost... then optimized. Yes, I can use if elif else blocks and I can and do use dictdispatch tables.... but I "wish" there were a switch case default block inpython so that some things could be coded to be clearer to a human reader.

just sayin... not arguing though... just a comment.
 
M

Mark H. Harris

Does your switch construct have to handle the magic of GT_1 meaning ">
1", or do you first figure out where it falls with an if/elif tree?

hi Chris, yeah... well think again of the switch block in C... the switch block selects a branch based on an integral number (int character) that is generally a return code from a function. The function hides all of thatlogic. The function runs and returns a "number" which is passed to the switch block. That number generally corresponds to a DEFINE constant at the top or in the header... so we get something really readable:

x = somefunc()
switch (x):
case: CONSTANT1
call blah blah
case: CONSTANT2
call blah blah blah
default
blah

This very same concept can work in python code too... if everyone would just agree to try. Again, how it switches is less important than does the "switch" appear readable to humans... whether its optimized or not.

Its up to the devs whether it switches on an int, a "switch object" whatever I mean by that, or on something else I have not thought about... the point is ,

... can we make something more readable to average people than large ifelif else chains, or dict dispatch tables... ??

Its just a question. I know Guido thinks not... and a lot of other people too... but what if there is a third option? If we think about this hard enough there is probably a win win out there that would work/

just sayin

marcus
 
C

Chris Angelico

hi Chris, yeah... well think again of the switch block in C... the switch block selects a branch based on an integral number (int character) that is generally a return code from a function. The function hides all of that logic. The function runs and returns a "number" which is passed to the switch block. That number generally corresponds to a DEFINE constant at the top or in the header... so we get something really readable:

x = somefunc()
switch (x):
case: CONSTANT1
call blah blah
case: CONSTANT2
call blah blah blah
default
blah

Okay. So you'd do that to make this more readable. Here's the original:

if (n**2 < D(1)):
a = __atan__(n)
elif (n == D(1)):
a = gpi/4
elif (n == D(-1)):
a = -(gpi/4)
elif (n < D(-1)):
a = __atan__Lt_neg1__(n)
else:
a = __atan__Gt_1__(n)

Now let's see. Your code doesn't perfectly translate, so I'm having to
guess a bit here.

GT_1 = object()
LT_n1 = object()
IS_1 = object()
IS_n1 = object()
def categorize(n):
if (n**2 < D(1)):
return None
elif (n == D(1)):
return IS_1
elif (n == D(-1)):
return IS_n1
elif (n < D(-1)):
return LT_n1
else:
return GT_1


switch categorize(x):
case GT_1:
__atan__Gt_1__(x)
# Not sure which you mean, this
case LT_n1:
__atan__Lt_neg1__(x)
# or this
case LT_1:
__atan__Lt_1__(x)
# So I have both, ish.
case IS_1:
a = gpi/4
case IS_n1:
a = -gpi/4
default:
# Presumably this should be a=?
__atan__(x)


Please copy and paste this, and then edit it so the two actually do
exactly the same thing. And then, if you please [1], explain to me how
the second one is more readable. It still has the exact same if/elif
tree, because there's no other way to figure out which constant you
should have for the switch; and as well as that, it exhibits an
appalling degree of Soft Coding[2], and it's roughly three times as
much code. If you want to change anything, you potentially have to
edit three places: the list of constants at the top, the condition
function, and the switch.

This can't be your idea of readability. Show me where I'm wrong.

ChrisA

[1] Steven D'Aprano unwittingly quoted HMS Pinafore a few posts back.
I'm now doing it consciously, for I hold that (on the seas) the
expression "if you please" a particularly gentlemanly tone implants.

[2] http://thedailywtf.com/Articles/Soft_Coding.aspx
 
M

Marko Rauhamaa

Ben Finney said:
Since you don't care about identity, only that the objects have
different values, you should be comparing for equality with ‘==’.

Ok, one last attempt.

I need *identifiers*. I could simply define:

class ABC:
A = object()
B = object()
C = object()

The program would work perfectly.

Except, if it's got a bug. I know self.abc contains either A, B or C,
but which one? Printing out self.abc won't give me any help. I could
print out ABC.A, ABC.B and ABC.C and see which one matches, but that's
cumbersome.

The next variant is to use objects that have names:

class Symbol:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name

class ABC:
A = Symbol("A")
B = Symbol("B")
C = Symbol("C")


The same program still works (and still uses "is" for identity tests,
mind you).

The next realization is that the Symbol class is completely redundant as
any string object would fit the bill. Hence we can reduce the visual
clutter by simply defining:

class ABC:
A = "A"
B = "B"
C = "C"

The same program still works (and still uses "is" for identity tests,
mind you).


Marko


PS The only remaining redundancy is having to repeat the symbol name in
every assignment. If Python (like Lisp) had a builtin symbol datatype,
you wouldn't have to define anything at all but simply assign something
like this:

self.abc = $A

where $A would be a symbol.
 
M

Marko Rauhamaa

Steven D'Aprano said:
But I'm not sure that there is a good reason to put the class definitions
inside the __init__ method. That means every time you create a new
StateMachine instance, the classes have to be re-created.

[...]

It will be much more efficient if you pull all the Idle, etc. classes out
and make them top-level global classes.
[...]
But then the closure over sm won't work...

Precisely.

As for effiency, I don't know if it is more efficient to create ad hoc
classes or ad hoc instances; might be a tossup in CPython.
Since in this example you've actually got significant behaviour in the
states, they aren't just symbols, and some sort of solution along
these lines (whether you use multiple inner classes or not) is
appropriate.

That inner class solution is an alternative to the symbol approach. In
the absense of a switch statement, the inner class has lots going for
it.


Marko
 
M

Marko Rauhamaa

Ben Finney said:
Use ‘==’, since that's all that matters for getting a value that will
work fine.

You are telling me to use '==' if I choose string objects and 'is' if I
choose some other objects.

I prefer a solution that works regardless of what objects I choose for
identifiers.

There really is no taboo against string object identity if you know what
you are doing.


Marko
 
C

Chris Angelico

You are telling me to use '==' if I choose string objects and 'is' ifI
choose some other objects.

I prefer a solution that works regardless of what objects I choose for
identifiers.

There really is no taboo against string object identity if you know what
you are doing.

And, as proven here in this thread, you do not know what you are doing.

ChrisA
 
M

Michael Torrie

You are telling me to use '==' if I choose string objects and 'is' if I
choose some other objects.

No, '==' works fine no matter what objects you assign to your state
variables.

class Foo(object):
STATE1 = object()
STATE2 = "testing"
STATE3 = 2

def __init__(self):
self.state = Foo.STATE1

def bar(self):
if self.state == Foo.STATE1:
pass
elif self.state == Foo.STATE2:
pass
elif self.state == Foo.STATE3:
pass
I prefer a solution that works regardless of what objects I choose for
identifiers.

As shown, '==' does work for this too. I don't know which is more
correct, but it does work.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,078
Messages
2,570,570
Members
47,204
Latest member
MalorieSte

Latest Threads

Top