Ways to use less if else?

Summary

Is there any good ways to control the behaviour of widgets when no action are taking,I want to control that no action no response,I have to use if else to control the behaviour,there for lots of if else in the code make the code is very dirty,would like to see some great solutions.

Steps to reproduce

Code snippet:

options = st.multiselect(
    'please select factors for analysis',
    ["Zscore"]
)
if options is not None:
    # do something 
else:
    # do nothing and do not perform any code in the back of this 

If applicable, please provide the steps we should take to reproduce the error or specified behavior.

Expected behavior:
any way do not usedso many if else
Explain what you expect to happen when you run the code above.

Actual behavior:

Explain the undesired behavior or error you see when you run the code above.
If you’re seeing an error message, share the full contents of the error message here.

Debug info

  • Streamlit version: (get it with $ streamlit version)
  • Python version: (get it with $ python --version)
  • Using Conda? PipEnv? PyEnv? Pex?
  • OS version:
  • Browser version:

Requirements file

Using Conda? PipEnv? PyEnv? Pex? Share the contents of your requirements file here.
Not sure what a requirements file is? Check out this doc and add a requirements file to your app.

Links

  • Link to your GitHub repo:
  • Link to your deployed app:

Additional information

If needed, add any other context about the problem here.

Just remove the else.

if options is not None:
    # do something 

If the program is checking in the list of strings, maybe try using switch command?

This is actually a very good question - first, let me explain why:

Guido himself (inventor of python) stated that if he could, he would take back the implementation of else, in fact, he openly admits he stole the else and elif concepts from C

https://twitter.com/gvanrossum/status/1142480012848717824

Historical concepts aside, lets look at why the else pattern is dirty in some cases and what we can do differently:

lets say we have a pattern where we are handling different situations in a logical cycle, for example perhaps we have an api that gets a post request, and depending on a type field passed to a filter, we might handle different cases:

lets first visualize an example of this with fastapi and a social media application in which we have posts, in this example we will have three primary types of post feed that we can access -

  • trending
  • following
  • interests

@router.post(
    "/get_post_feed",
    response_model=schema.PostFeedResponse,
    dependencies=[Depends(JWTBearer())],
)
async def get_post_feed(context: schema.GetFeed, request: Request):
    """
    The get_post_feed function returns a list of posts that are sorted by the
    time they were created. The function takes in a user id and an optional page
    number and size to determine where to start the list of posts, as well as how
    many posts should be returned. If no user id is provided, it returns all public
    posts.

    :param context:schema.GetFeed: Used to Pass in the filter type.
    :param request:Request: Used to Get the user id of the logged in user.
    :return: A response.
    """
    user = request.user
    content_filter: str = context.context.filter
    response = None
    if content_filter == "following":
        response = get_latest_posts(user.id, context.params.page, context.params.size)
    elif content_filter == "interests":
        user_interest_list = [
            x.asset_type_id for x in Interest.get_interests_by_user(user.id)
        ]
        response = get_trending_posts_by_interest(
            user.id, user_interest_list, context.params.page, context.params.size
        )
    elif content_filter == "trending":
        response = get_trending_posts(
            request.user.id, context.params.page, context.params.size
        )
    logger.info(f"response payload: {response}")
    return response or {"items": [], "total": 0, "page": 1, "size": 1}

now lets define our interface models


class Post(BaseModel):
    """
    It's a model that represents a post.
    """

    id: UUID
    content: str
    created_at: datetime.datetime
    post_likes: int
    post_comments: int
    liked: bool = Field(default=False)



class PostFeedResponse(BaseResponse):
    """
    It's a model that represents a response from the feed endpoint.
    """

    items: List[Post]
    page: int
    size: int


class Filter(BaseModel):
    """
    It's a model that represents a filter.
    """

    filter: str

    class Config:
        schema_extra = {"example": {"filter": "following"}}


class Params(BaseModel):
    """
    It's a model that represents the parameters of the feed endpoint.
    """

    page: int
    size: int

    class Config:
        schema_extra = {"example": {"page": 1, "size": 3}}


class GetFeed(CamelModel):
    """
    It's a model that represents the parameters of the feed endpoint.
    """

    class Config:
        schema_extra = {
            "available choices": [
                "trending: The top users trending",
                "or",
                "following: The top weighted people you follow",
                "or",
                "interests : The top assets you are interested in",
            ]
        }

    context: Filter
    params: Params

great simple enough - we will skip the other functions like the db methods and such, they dont matter so much here - what matters is that we have three OR flows, only one can execute at a time. in the example provided we handle this with a switch-like method - elif

now, elif is not hacky, but its also not the best way to implement this, why?

if else if, else if is not necessary to achieve our flow - let me explain:

user = request.user
    content_filter: str = context.context.filter
    
    if content_filter == "following":
        # explicitly return the value of the function rather than assigning it to a variable
        return get_latest_posts(user.id, context.params.page, context.params.size)
    
    if content_filter == "interests":
        user_interest_list = [
            x.asset_type_id for x in Interest.get_interests_by_user(user.id)
        ]
        return get_trending_posts_by_interest(
            user.id, user_interest_list, context.params.page, context.params.size
        )
    if content_filter == "trending":
        return get_trending_posts(
            request.user.id, context.params.page, context.params.size
        )
    
    # express the default case explicitly
    return {"items": [], "total": 0, "page": 1, "size": 1}

in this example now, we have changed the code so that we are no longer assigning a variable, and actually break the request scope through explicit returns - in this case, we are narrowing the scope of the control flow to ensure less moving parts - notice how in the previous flow we had to log and capture the response object for analysis - this is not a bad practice, but an unnecessary one.

lastly, we have an explicit return value defined to handle what might have otherwise been an else case - that is to say, we know all of the possible rules of the flow and are able to plan around them.

this, more than anything, is expressly the point of why Guido said he would not have implemented else or elif - its not that they are not useful for flow control, its that as we plan our use case better we begin to realize that polymorphism and ambiguous use case are the real death of maintainability - by understanding all use cases and business rules before implementation, we gain the ability to simplify our flows and make our code more beautiful.

im not saying never use elif or else, im saying if you can not avoid their use, try to think bigger and ask why you need to handle abstract flow controls.

I would like to point out that this nullcheck is also not actually required -

if options is not none:

can be more simply expressed as

if options:

and given the simplicity of this expression, now we can expand to get something close to null type coalescence

perl had a great name for this type of operation actually - the Logical Defined-Or operator

this gives us a great hint in how to use it in a pythonic way -

if options or some_other_method 

gives us a null check and a else case at the same time, further exploring this a little, we could use the assignment operator (walrus) to further provide optional behavior:

if options := some_method() or some_other_method():

in this case were assigning it while checking against its possibility of null in the same operation - one further note on this subject, we could perhaps also use the and operator to provide some expansive flow control for example

if predicate_method() and options := some_method() or some_other_method():

would require the predicate_method to be true and options to be defined for some_method to resolve, and if not some_other_method would resolve

we can see how these advanced flow control methods let us clean up our code and combine many potential else and else if operations into one operation without really sacrificing maintainability HOWEVER - i will note that by combining these operations we DO reduce our ability to debug, and if it costs us one more line of code to debug better, sometimes the tradeoff is worth it!