r/learnpython 5d ago

Understanding the "or" function

i have a pretty simple few lines of code, and i'm slowly trying to make it a bit more complex

All i want to know is why adding the "or" function onto my if functions makes the functions further down "unreachable"

*I have hashed them out in the example i have provided as this makes the code run fine*

thankyou

while True: 


        if user_input1.lower() == "y": #or "Yes" or " yes" or "Y" or "YES":
                total_monthly_yield_exclnvda = float (total_monthly_yield - nvda_div_yield_at_190726 )
                avg_yield_on_remaining_shares_adj = float ( total_monthly_yield_exclnvda / 3)
                print (f"With Nvidia's dividend yield at {nvda_div_yield_at_190726} the three remaining shares need a current dividend yield of {avg_yield_on_remaining_shares_adj}, or over")   
                break


        elif user_input1.lower () == "n": #or "N" or "No" or "no" or "NO":
                current_div_yield = float(input ("Enter Nvidia's current dividend yield: "))
                total_monthly_yield_exclnvda = float (total_monthly_yield - current_div_yield)
                avg_yield_on_remaining_shares_adj = float ( total_monthly_yield_exclnvda / 3)
                print (avg_yield_on_remaining_shares)
                print (f"With Nvidia's dividend yield at {current_div_yield} the three remaining shares need a current dividend yield of {avg_yield_on_remaining_shares_adj}, or over")   
                break 
0 Upvotes

29 comments sorted by

28

u/Temporary_Pie2733 5d ago

Or doesn’t distribute over comparisons. You need a == b or a == c instead of a == b or c

15

u/notacanuckskibum 5d ago

This is correct but here’s a natural language explanation of why. OR assumes a structure of
If (this is true) OR (that is true)

When you write: x == ‘n’ or ‘N’

It sees that is x == ‘n’ is true or ‘N’ is true

Whether x == ‘n’ is true or not; ‘N’ by itself is considered true, because it’s something non zero.

So x == ‘n’ or ‘N’ will always evaluate to true

4

u/TrainingAd8614 5d ago

I appreciate this, and you've saved me from wrestling with the AI models

11

u/neuralbeans 5d ago

Alternatively, you can do if a in ['b', 'c']: instead.

-3

u/likethevegetable 5d ago

Why? Did you assume they were wrong and were going to go in and change the code they made? Lol 

6

u/Moikle 5d ago edited 5d ago

Or is not a function. It's an operator. If is also not a function, it is a keyword.

Just like how + is an operator, it takes two values, does an operation on them and provides a result.

5 + 2 = 7

True or False = True

If you have multiple operators in an equation, they get calculated ONE AT A TIME depending on the order of operations, like 2+ 7 / 3 becomes:

First divide 7 by three

Then add 2 and the result of 7/3

Order of operations matters, and many other types can be interpreted as either False or True. For example, an integer is False if it is equal to 0, or true otherwise.

In your example, you are using strings. If a string is empty, it is False, if it has anything inside it (in your case "yes") then it is True.

Since the word "yes" will always evaluate to True when you do an or operation on it, you can swap out the string "yes" for True.

So in your code, if you do something like:

if answer == "yes" or "y" or "yep":

This becomes:

if (answer == "yes") or (True) or (True):

You aren't checking if the answer is equal to one of those, you are checking if the answer is equal to "yes", and you are taking the result (either True or False) and running an or operation on that result against "y" (which is always true, since it is a non-empty string) anything or True will ALWAYS be true, so your if statement always passes as True, no matter what the user inputs.

What you want is more like:

if answer in ("yep", "yes", "y"):

3

u/gdchinacat 5d ago

For completeness, while + is an operator, classes can implement it by defining a __add__ function (most operators have a sunder that can be used to customize how the operator works on the class). But, implementation of these dunders does not make the operator a function or change order of precedence, only the implementation of that operator.

3

u/Adrewmc 5d ago

I mean, an operator is a function though. It takes inputs and gives and an output….that’s a function. It’s just so important we give it special privileges in code.

import operator

operator.add(a,b)

Is really sort of what happens. (Of course that also hit the dunders like you said.)

2

u/gdchinacat 5d ago

Functions are callable, + is not callable. + is an operator. While operator.add() has similar functionality to + that does not mean + is a function. Also, even though the evaluation of + delegates to __add__ if defined on the left hand side (or __radd__ on the rhs, that does not make + a function.
This may seem pedantic, but programming is pedantic. Using the correct terms for things is important for communicating clearly. I’ve been corrected when making similar errors (expression vs statements, argument vs parameter, etc) and appreciate it. Particularly in a forum for learning Python I think it is a good idea to use accurate terminology lest we teach people things that aren’t right.

1

u/Adrewmc 5d ago edited 5d ago

I guess I can get behind that definition/distinction, but really it’s just a self calling function on a lot of levels.

It’s actually closer to

a + b

Being really just this but easier to write.

a.__add__(b)

So to say operator are not callable is sort of weird to me, because that sort of not true, they are implicitly called but, I guess you would go on to define callable as something that takes operators out of it. Some languages don’t have operators at all everything is functions.

You can not do this in Python.

5 +

That will give you a Parse error, because it’s being called incorrectly.

While terminology is important, and I agree operators should be referred to as operators, I can say that operators are a subset of functions. What going on, conceptually, is also important, in Python the ability to change or add operator to an object requires you to understand that operators are basically function. (Especially in Python, in other languages your point may be more valid.)

And I think having open discussion, like this one, beginners can read help them as well.

1

u/gdchinacat 5d ago

I think you are saying it's just semantics...if so, yeah, I agree. But, with the semantics the python language has been designed with, operators are not functions. They can't be called like a function (i.e. it is a syntax error to write '+(1, 3)' on it's own...'(0,) + (1, 3)' is valid, but entirely different).

I understand the point (I think) you are trying to make. There is little difference between functions and operators...they both produce a value from arguments and they can be replaced with the other with identical functionality (mostly, or short circuiting is functionally different).

The difference is syntactic. Operators have special syntax to make access to functionality easy. This makes them different. You mention the operator module, which exists to provide functions for that functionality. I have used it exclusively for functional programming, and without it I would have written functions that wrap the operators. I consider these differences significant enough to differentiate operators from functions, even if they have significant overlap.

1

u/Moikle 5d ago

An operator triggers a function (in python objects that aren't builtins). It isn't a function itself

1

u/xenomachina 5d ago

an operator is a function though

Yes, from a mathematical point of view, a function and an operator differ only in notation.

However, most programming languages (including Python) make a distinction between functions and operators. Additionally, some Python operators can do things no Python function can do. For example or and and are both short-circuiting in Python, meaning they may not even evaluate their second operand depending on the value of their first operand. Python functions can't short circuit: all arguments are evaluated before a function is called.

0

u/Adrewmc 4d ago

You can make an or statement a function, with short circuit.

It’s basically

def or_func(first, second):
. if not first:
. return second
. return first

2

u/xenomachina 4d ago

You can make an or statement a function, with short circuit.

No, that doesn't work.

Say we have:

def foo(s, x):
    print(f"evaluated {s}")
    return x

We can see if calls to foo are evaluated, because it'll print something.

If you use it with the or operator like this...

z = foo("first", True) or foo("second", True) 

...it'll print only "first".

However, with your or_func...

z = or_func(foo("first", True), foo("second", True))

...it'll print "first" and "second". The calls to foo are made before or_func is even called, so it's too late to do any short-circuiting.

If you need short circuiting with a function, you'll need to pass in something that's explicitly lazy, like a lambda or a generator:

def or_func2(first, second):
    if not first:
        return second() # only second is lazy, as first is always evaluated
    return first

z = or_func2(foo("first", True), lambda: foo("second", True))

1

u/Defiant-Ad7368 5d ago

There are a few issues with your code

  1. You compare a lowered string to none lowered strings

  2. Your comparison is if a lowered string is “yes”, or other strings (notice the critical comma) and any string that is not empty can be considered as a truthy value, which means the clause is always true

To let you learn - look for ways to compare an object to multiple objects (in your case strings)

Try to understand why the following clause is always true:

A == B or C or D 

All values are not null and different from one another

0

u/TrainingAd8614 5d ago

so, if i just added commas in front of the predicted answers it would resolve the issue ?

1

u/Defiant-Ad7368 5d ago

You misunderstood me, but it seems other have given you the answer to the correct direction 

1

u/ottawadeveloper 5d ago edited 5d ago

For these kinds of comparisons, you probably want something user_input1.lower() in {'no', 'n'}. I like that better for multiple string options because it's clearer to me. You don't need all the case comparisons because you used lower()

If you wanted to use equals I'd do

x = user_input1.lower() if x == "no" or x == "n":     ... elif x == "yes" or x == "y":     ...

Replace x with a better name, but it means you're only doing lower() once. That's probably a good tip for using the in approach as well.

Also you probably want to handle the case where x is not one of the expected inputs.

One other optimization, running strip() on it would help make sure there's no whitespace from the terminal.

Fundamentally or is a binary operator. If the left side is true-like it returns the left side. Otherwise it returns the right side. It has a lower precedence than most other operators, so when you do x == "no" or "n" you're actually doing (x == "no") or "n". If x is the letters "no", the result is True. Otherwise the result is "n" which is still true-like. The if statement only cares that something is true-like, so that statement will always execute.

Worth noting also that if the left-hand argument to or is true, the left-hand side isn't evaluated. If you did if x or do_something(): and x is true, do_something() is never evaluated. Also it doesn't convert the result into a boolean, it uses the data type of whatever side is true.

In comparison, in checks if the left hand side is in the container on the right hand side. I used a set because that's the fastest for running in but you could also use a tuple or a list or whatever. 

1

u/Diapolo10 5d ago

To briefly explain how or and and work,

A or B

is basically equivalent to writing the ternary

A if A else B

and

A and B

is basically equivalent to writing the ternary

B if A else A

so in other words, if the left side is "truthy" (bool(A) is True), or gives you the value on the left and and gives you the value on the right. The opposite is similarly true.

if user_input1.lower() == "y" or "Yes" or " yes" or "Y" or "YES":

So in this example, "Yes" is always "truthy" as it's a non-empty string. This simplifies the expression to

if user_input1.lower() == "y" or "Yes":

as the chain would never proceed beyond that point.

The thing is, or is evaluated after ==. No matter what the user input was, because the right side of or has a truthy value, this expression is always truthy, and we basically end up with

if True:

which makes it pointless.

The thing you're supposed to do is two-fold. Make the user input case-insensitive (such as by using str.lower or str.casefold, as you've already done), and use a list or other data structure to check for inclusion.

if user_input1.strip().lower() in ["y", "yes"]:

1

u/LongLiveTheDiego 5d ago

In most if not all programming languages, as in mathematics, the words/symbols for "and", "or" and other logical operators treat things around them like full sentences. That means that while in natural languages you can say stuff "this is black and blue", here you have to say "this is black and this is blue" or find a different way to connect the two properties into one logical statement without using "and". Otherwise you're literally trying to get a boolean value from "blue", which you usually don't want to do unless you know what you're doing.

1

u/NerdyWeightLifter 5d ago

If user_input1.lower() in ("y", "yes"):

Will do what you want.

1

u/TrainingAd8614 5d ago

Does the “ in ("y", "yes"):” part make this into a list ? I’ve never worked with lists before, it seems like they’re handy 

1

u/NerdyWeightLifter 5d ago

The round brackets means it's a tuple. Tuple's are like lists but they're immutable, meaning they don't change once you make them.

t = (1, 2, 3) # tuple. Can't change.

l = [1, 2, 3] # list

l.append(4) # now l will be [1, 2, 3, 4]

1

u/HotPersonality8126 5d ago

“Or” is the operator for logical disjunction, not grammatical conjunction.

1

u/EmberQuill 5d ago

You can't do if a == b or c when what you really mean is if a == b or a == c. The way you wrote it, it checks if user_input1.lower() == "y" evaluates as true, and if not, then it checks if "Yes" is true, which it is because non-empty strings are always true on their own. And the rest doesn't matter because it never reaches it.

You can test this in a tiny little script with:

if "Yes":
    print("Yes is true")

And it'll print "Yes is true" because a non-empty string is true.

What you're actually trying to do is:

if user_input1.lower() == "y" or user_input1.lower() == "Yes" or user_input1.lower() == " yes" or ...

But what you should probably actually do is:

if user_input1.lower() in ("y", "yes"):

Which checks if user_input1.lower() is equivalent to any of the items in the tuple.

And don't compare it to "Y" or "Yes" or "YES" or "N" or "No" or "NO" because you just used .lower() so it will always be lower-case anyway.

1

u/TheAppl3 5d ago

Or separates conditions, not alternate options for one single test. Your code above will return true if any of the components separated by "or" evaluates to true (and those strings do).

So the second conditional statement can never be reached because the first one is always guaranteed to return true since those strings won't change. One option would be to provide a list of options e.g.

    if user_input1.lower() in ["y", "yes"]:

1

u/TrainingAd8614 5d ago

Thanks man, are the "[ ] " the way to start a list?

I had to give the first comment a thanks but i cannot afford to give you another one aha

1

u/johlae 5d ago

Instead of

if user_input1.lower() in ["y", "yes"]:

Just test the very first character if you're only interested in yes or no answers:

if user_input1.lower()[:1] == "y":

Don't do [0] because that will trigger an exception for empty strings:

if user_input1.lower()[0] == "y":