Can global variable be passed into Python function?

M

Marko Rauhamaa

Chris Angelico said:
In theory, yes. If that's all people will ever do, then you can safely
use == to check. Why are you using is?

The main reason to use "is" is to indicate that the object is a sentinel
object whose identity is what is meaningful, not the content. Using ==
would work but would give some poor soul the idea that the state
variable could hold any imaginable string.

The implementation should be able to change the state objects to any
(distinct) objects at all (say, the new enums, or ints, or some more
elaborate class instances) without any changes in the code.


Marko
 
N

Neil Cerutti

It's easy have a "cultural aversion" when the language doesn't
provide the facility.

Switch statements provide for excellent readability in parsers
and state machines, for example. They also allow the Python
compiler to optimize the statement internally unlike long
if-else chains.

Once you remove all the features of switch that wouldn't fit in
Python, e.g.; fall-through, jumps, breaks; whats left provides
negligible benefit over if-elif-else or dict-dispatch.

A pythonic switch statement doesn't provide enough features to
bother with its implemention.

Check out Go's switch statement for an example of what it might
look like in Python. Except you'd get it without labeled break or
the fallthrough statement. Would you still want to use it?
 
G

Grant Edwards

Here's a use case for "is" with strings (or ints):

class Connection:
IDLE = "IDLE"
CONNECTING = "CONNECTING"
CONNECTED = "CONNECTED"
DISCONNECTING = "DISCONNECTING"
DISCONNECTED = "DISCONNECTED"

def __init__(self):
self.state = IDLE

def connect(self, address):
...
self.state = CONNECTING
...

def disconnect(self):
...
if self.state is CONNECTED:
...

I don't really see the point. Why won't '==' work just as well?

Are you hoping that 'is' is faster at runtime than '=='?
 
M

Marko Rauhamaa

Neil Cerutti said:
Check out Go's switch statement for an example of what it might
look like in Python. Except you'd get it without labeled break or
the fallthrough statement.

No need for the fallthrough (except that multiple cases should be
supported).

Labeled breaks wouldn't be needed because there are no fallthroughs.
Would you still want to use it?

Probably.

Guile (scheme) has:

(case (state self)
((CONNECTING CONNECTED)
...)
((DISCONNECTING)
...)
(else
...))

Python isn't "averse" to the switch statement because it would be not
that useful. Rather, the problem is that Python doesn't have nonliteral
constants (scheme has builtin symbols). It is difficult to come up with
truly Pythonic syntax for the switch statement.

Something like

switch self.state from Connection.State:
case CONNECTING or CONNECTED:
...
case DISONNECTING:
...
else:
...

would be possible, but here, "Connection.State" is evaluated at compile
time. Don't know if there are any precedents to that kind of thing in
Python.


Marko
 
N

Neil Cerutti

Right. I would like, ideally, for the Python documentation to
avoid mentioning that term entirely; and I would hope for that
to promote a better understanding of Python's data model.

The wider programming community, though, will no doubt continue
to use that term to refer to various (incompatible) data
models, and I certainly don't expect the Python community to
pretend it doesn't exist.

I like the characteristic of Python that assignment and argument
passing work the same way. If only C were so simple!

The tutorial makes things sound more high-falutin' than that
[Tutorial 4.6 Defining Functions]:

The actual parameters (arguments) to a function call are
introduced in the local symbol table of the called function
when it is called; thus, arguments are passed using call by
value (where the value is always an object reference, not the
value of the object). [...]

How about:

The actual parameters (arguments) to a function call are passed
via assignment to the variables in the local symbol table of
the called function.

Am I oversimplifying?
 
C

Chris Angelico

Python isn't "averse" to the switch statement because it would be not
that useful. Rather, the problem is that Python doesn't have nonliteral
constants (scheme has builtin symbols). It is difficult to come up with
truly Pythonic syntax for the switch statement.

Something like

switch self.state from Connection.State:
case CONNECTING or CONNECTED:
...
case DISONNECTING:
...
else:
...

would be possible, but here, "Connection.State" is evaluated at compile
time. Don't know if there are any precedents to that kind of thing in
Python.

Can you elaborate on this "nonliteral constants" point? How is it a
problem if DISCONNECTING isn't technically a constant? It follows the
Python convention of being in all upper-case, so the programmer
understands not to rebind it. Is the problem that someone might
(naively or maliciously) change the value of DISCONNECTING, or is the
problem that Python doesn't fundamentally know that it won't change?

ChrisA
 
R

Roy Smith

Python already has a switch statement. It's just spelled funny...

class Switch(Exception): pass
class Case1(Switch): pass
class Case2(Switch): pass
class Case3(Switch): pass

try:
raise value
except Case1:
print "did case 1"
except (Case2, Case3):
print "did either case 2 or 3"
else:
print "did default"

No fall-through, however. I'm sure with a little meta-class magic, you
could write a Case() which eliminates the need for manually declaring
the cases.
 
M

Michael Torrie

Steven D'Aprano said:
class Connection:
IDLE = "IDLE" [...]
CONNECTED = "CONNECTED" [...]
def disconnect(self):
...
if self.state is CONNECTED:
...

Why do you care that the state is *that specific* string, rather than
any old string with the value "CONNECTED"?

I can think of a reason:

* When you publish the API for the ‘Connection’ class,

* and another party writes code that sets ‘state’ to a string with the
value ‘"CONNECTED"’,

* and you implemented the check as ‘self.state == "CONNECTED"’,

* and their code works with your class and it goes into production,

* you're then not able to change the expected value without breaking
that party's code.

Sure. If he replaced the line if self.state is CONNECTED with if
self.state == self.CONNECTED then he is free to change CONNECTED at any
time. So yes, "is" is not necessary here. Equality checking works fine.
 
M

Mark Lawrence

Switch statements provide for excellent readability in parsers and state
machines, for example. They also allow the Python compiler to optimize
the statement internally unlike long if-else chains.

There are umpteen recipes for switch statements so take your pick or if
you don't like any of them write your own. Much easier than beating
your head against multiple brick walls, which is what raising this one
on python-ideas is likely to be. See
http://legacy.python.org/dev/peps/pep-0275/ and
http://legacy.python.org/dev/peps/pep-3103/
 
M

Marko Rauhamaa

Chris Angelico said:
Can you elaborate on this "nonliteral constants" point? How is it a
problem if DISCONNECTING isn't technically a constant? It follows the
Python convention of being in all upper-case, so the programmer
understands not to rebind it. Is the problem that someone might
(naively or maliciously) change the value of DISCONNECTING, or is the
problem that Python doesn't fundamentally know that it won't change?

This last point. It would make it impossible for Python to treat the
switch statement as anything but an alternate form of chained if-else.
A dict optimization wouldn't actually optimize anything because it would
have to be constructed every time the statement is executed.

switch self.state from Connection.State:
case CONNECTING or CONNECTED:
...
case DISONNECTING:
...
else:
...

would have to be transformed by Python into:

_X1 = self.state
_X2 = Connection.State
if _X1 is _X2.CONNECTING or _X1 is _X2.CONNECTED:
...
elif _X1 is _X2.DISCONNECTING:
...
else:
...

So optimization is gone. Then we have the syntactic burden. Python
currently doesn't (seem to) have a syntactic precedent for such implicit
dot notation. (Note that even Java had to complicate its syntax
analogously with enums.) In "CONNECTING or CONNECTED", "or" wouldn't be
an operator in an expression but a particle.

Another syntactic oddity is the two indentation levels.

BTW, here's a syntax that doesn't introduce any new keywords:

with self.state from Connection.State:
if CONNECTING or CONNECTED:
...
elif DISONNECTING:
...
else:
...


Marko
 
C

Chris Angelico

BTW, here's a syntax that doesn't introduce any new keywords:

with self.state from Connection.State:
if CONNECTING or CONNECTED:
...
elif DISONNECTING:
...
else:
...

Okay, I understand your 'from' now. What it really does is introduce a
new scope, a read-only one presumably (because you really do NOT want
the Pandora's Box that ECMAScript's 'with' is) from which unqualified
names will be looked up. I would say that that's a very reasonable
idea, quite separately from a switch statement. Suppose you had
something like this:

with scope(Connection.State):
if self.state == CONNECTING:
print("I am not",DISCONNECTING)

It'd require a change to the LOAD_GLOBAL opcode to have it look in
multiple scopes. If you want to change something, be explicit about
where the change goes, but for lookups, it would be possible to have
them go to multiple places. I suspect, though, that this wouldn't fly;
I already posited such a theory, and was told that CPython's internals
made it much more convenient to not introduce infinitely nesting
scopes - the two use-cases that I'd most look at are these:

# This executes as a function
doubled = [x*2 for x in lst]

# This implicitly unbinds e in a finally clause
try:
foo()
except Exception as e:
pass

Neither is quite perfect; the closure method is mostly clean, but has
some extremely esoteric edge cases, and the unbinding means that a
previous value for 'e' is lost. But both are kept rather than
introducing this concept of true subscoping, because CPython's
implementation makes the latter hard.

Predicating your entire proposal on something that has been avoided
twice and just recently turned down, though, is a good way to get the
whole proposal rejected.

ChrisA
 
G

Grant Edwards

This last point. It would make it impossible for Python to treat the
switch statement as anything but an alternate form of chained if-else.

There are a couple nice things about a switch () statement:

1) It guarantees that 'expr' is evaluated exactly once. If you want
that with a chained if/else you have to create a temporary variable.

2) It guarantees that exactly one path is chosen (assuming we're not
going to duplicate C's "fall through" mistake).

The result is that it makes the author's intention instantly clear to
the reader: we're going to evaluate some expression _exactly_once_ and
then select _exactly_one_ of several paths based on that value. Sure,
you can do the same thing with a chained if/elif/else, but it requires
some effort for the reader to figure that out. Accidently type "if"
instead of "elif" two thirds of the way down, and you get something
that works right _most_ of the time, but not always. [Not that _I've_
ever done that and then read through the whole thing six times over a
period of two days before noticing it.] :)

<reductio ad absurdum>
If the availability of an alternate but computationally equivalent
representation was a valid argument against a language feature, then
we ought to toss out Python entirely: It's always possible to write
the exact same algorithm, so why bother with Python?
A dict optimization wouldn't actually optimize anything because it
would have to be constructed every time the statement is executed.

I don't think question should be "how does this help the compiler?"

The question should be "how does this help the _user_ of the compiler?

The user of the compiler spends more time reading code than anything
else. Something that makes code easier to read is therefore worth
considering. A switch statement is easier to read than a chained
if/else. IMO, _that's_ the proper argument for a switch statement.

Trying to justify a switch statement as a way to help generate more
efficient code seems silly: This is not 1970 -- any decent compiler
should be able to generate the same code for a switch statement and
for an equivalent chained if/else.
 
M

Mark H. Harris


I have reviewed these peps, and I heard Guido's 2007 keynote, as well I have heard him speak on YouTube several times about the inadvisability of a pythonized switch statement (similar to C).

I think the real issue is about the syntax... because of python's unique indent strategy going back to ABC, a pythonized switch statement would play havoc with the many text parsers out there used for development (TestWrangler, and many others).

Personally I would still like to see a pythonized switch statement at some point. I prefer the syntactical form of PEP 275, but unlike the notion of dropping optimization and converting to if elif else under the proverbialcovers, I would prefer to see a conversion to the dict dispatch table under the covers.

At any rate... and I don't think even Guido can really argue against this,.... a switch statement is just more readable to human beings that a dict dispatch table, or a long if elif chain... and one of the main points of python (going all the way back to ABC) was to make very highly readable code.

marcus
 
M

Marko Rauhamaa

Mark H. Harris said:
I think the real issue is about the syntax... because of python's
unique indent strategy going back to ABC, a pythonized switch
statement would play havoc with the many text parsers out there used
for development (TestWrangler, and many others).

I also took a look at the proposals. I don't think it's the editor
issue. The variant I proposed most recently:

with self.state from Connection.State:
if CONNECTING or CONNECTED:
...
elif DISONNECTING:
...
else:
...

would be handled gracefully by all sane python editors, I believe.

The main problem is that it can't be optimized effectively without
bringing in an element of preprocessing. That preprocessing is done by
the human developer with the dict dispatch table, but nothing in regular
Python gives the compiler enough guaranteed information to build the
dispatch table. There are ways to be smart, but it would be a lot of
additional code for the compiler for a questionable performance gain.
a switch statement is just more readable to human beings that a dict
dispatch table, or a long if elif chain... and one of the main points
of python (going all the way back to ABC) was to make very highly
readable code.

A dict dispatch table is just awful. At least have the decency of
creating inner classes.

.... which brings up the point for another post...


Marko
 
C

Chris Angelico

A dict dispatch table is just awful.

Really? How is that? I've used them, often. Yes, there are times when
I could express something more cleanly with a C-style switch
statement, but other times the dispatch table is fundamentally
cleaner. I shared an example a few posts ago in this thread; care to
elaborate on how it's "just awful"?

ChrisA
 
M

Marko Rauhamaa

Chris Angelico said:
Really? How is that? I've used them, often. Yes, there are times when
I could express something more cleanly with a C-style switch
statement, but other times the dispatch table is fundamentally
cleaner. I shared an example a few posts ago in this thread; care to
elaborate on how it's "just awful"?

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?


Marko
 
M

Marko Rauhamaa

Mark H. Harris said:
Yep, my point exactly. nice illustration.

So now, for you and me: let's compare.

if key is ast.Assign:
return ' '.join(dump(t) for t in node.targets)
elif key is ast.AugAssign:
# Same target and same operator.
return dump(node.target) + dump(node.op) + "="
elif key is ast.Return:
# A return statement is always compatible with another.
return "(easy)"
elif key is ast.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")

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?


Marko
 
M

Mark Lawrence

So now, for you and me: let's compare.

if key is ast.Assign:
return ' '.join(dump(t) for t in node.targets)
elif key is ast.AugAssign:
# Same target and same operator.
return dump(node.target) + dump(node.op) + "="
elif key is ast.Return:
# A return statement is always compatible with another.
return "(easy)"
elif key is ast.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")

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?


Marko

http://c2.com/cgi/wiki?SwitchStatementsSmell
 

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
473,777
Messages
2,569,604
Members
45,216
Latest member
topweb3twitterchannels

Latest Threads

Top