1

I have a python file with many functions. Every function gets a client as the default argument, but on the stage of function initialization, it does not exist yet. He will be created in a main function inside with context manager.

How I can solve it (make client available in every function) and preserve a context manager as suggested in docs?

I tried to use global statement, generator, global variables, and functools.partial but all these attempts failed mostly because of a closed connection outside of the context manager.

...  # Many functions

def get_statistic_with(client: TelegramClient = client1):  # client1 is undefunded on this stage
    client.send_message(entity=config.BOT_NAME, message='/get_statistic_with')


def main():
    with TelegramClient('test_client`', config.API_ID, config.API_HASH) as client1, \
            TelegramClient('test_client1', config.API_ID, config.API_HASH) as client2:
    ...

I know that I can use a class to fix it but don't plan to create many instances, I only need to import and run the main function from another file. I also can place all the functions inside with code block but it will be too ugly. Passing a connection to every function is a pretty inconvenient.

1
  • How inconvenient would it be if the parameter did not have a default argument? Commented May 9, 2022 at 21:21

2 Answers 2

1

You mentioned trying both global variables and functools.partial (which works a lot like a lambda function), and neither worked.

I believe the same methods you tried would work if you used both of those together.

Here is an example where I've done that (I used lambda but if you prefer partial it should be an easy substitution):

class TelegramClient:
    def __init__(self, name):
        self.name = name

    def showProofOfWorking(self):
        print("--- SUCCESS! This is a TelegramClient named " + self.name)


# In order to avoid namespace errors, there must be a reference to the clients in global scope.
# However, the value of client1 doesn't need to be set until 'main', as will be demonstrated in the
# first of the three methods tested in this example code.
globalVars = {'client1': None, 'client2': None}
# For comparison purposes.  Compare how this globally scoped client reference acts relative to the function-scoped 'localClient' inside 'main'
otherGlobalClient = TelegramClient("OTHER CLIENT IN GLOBAL SCOPE")


def refersToGlobalClient(client = globalVars['client1']):
    client.showProofOfWorking()
    
def refersToGlobalWrappedClient(clientWrapper = lambda: globalVars['client1']):
    unwrappedClient = clientWrapper()
    unwrappedClient.showProofOfWorking()

def refersToLocalWrappedClient(clientWrapper = lambda: client1):
    unwrappedClient = clientWrapper()
    unwrappedClient.showProofOfWorking()


def main():
    client1 = TelegramClient('CLIENT 1')
    client2 = TelegramClient('CLIENT 2')
    # No references to this from the global scope:  it only exists inside 'main'
    otherLocalClient = TelegramClient('CLIENT ONLY IN LOCAL SCOPE')

    # Only 'client1' will be put into global scope.  'client2' is not defined outside the scope of 'main'.
    globalVars['client1'] = client1

    # -----  This is the only one of the three different methods tried here that works.  -----
    # It combines a global variable with a lambda function, which changes the reference of the wrapped global variable from a compile-time value to a runtime value,
    # circumventing the fact that at compile time, the value of the global variable will be None as it will not be set to its final value until inside 'main'.
    print("FIRST METHOD:  Global Scope plus Lambda Wrapper:  Works in All Cases")
    print("Inside main(), we will now call 'refersToGlobalWrappedClient' with its default argument, which is a function that returns the current value of 'globalVars['client1']'")
    refersToGlobalWrappedClient()
    print("\nNow we'll call the same function with a different argument from global scope.")
    refersToGlobalWrappedClient(lambda: otherGlobalClient)
    print("\nNow we'll call the same function with a different argument from local scope.")
    refersToGlobalWrappedClient(lambda: otherLocalClient)
    print("\n__________________________\n")

    print("SECOND METHOD:  Global Scope with No Wrapper:  Works With Arguments, Does Not Work With Default Parameter")
    print("Inside main(), we will now attempt to call 'refersToGlobalClient' with its default argument, which is 'globalVars['client1']'")
    try:
        refersToGlobalClient()
    except Exception as error:
        print("\nAN EXCEPTION OCCURRED!  Exception:")
        print(error)
    print("\nNow we'll call the same function with a different argument from global scope.")
    refersToGlobalClient(otherGlobalClient)
    print("\nNow we'll call the same function with a different argument from local scope.")
    refersToGlobalClient(otherLocalClient)
    print("\n__________________________\n")

    print("THIRD METHOD:  Local Scope with Lambda Wrapper:  ")
    print("Inside main(), we will now attempt to call 'refersToLocalClient' with its default argument, which is a function that returns 'client1'")
    try:
        refersToLocalWrappedClient()
    except Exception as error:
        print("\nAN EXCEPTION OCCURRED!  Exception:")
        print(error)
    print("\nNow we'll call the same function with a different argument from global scope.")
    refersToLocalWrappedClient(lambda: otherGlobalClient)
    print("\nNow we'll call the same function with a different argument from local scope.")
    refersToLocalWrappedClient(lambda: otherLocalClient)
    print("\n__________________________\n")


main()

Here, I tried three different methods. Like you said, only adding a reference to client1 in global scope, or only wrapping the reference to the not-yet-defined client1 inside a function, do not work and cause errors when you try to use client1 as a default argument.

But, if you both set up a reference to your client1 in global scope (here, it's inside a dict called globalVars) and wrap the default client1 parameter inside a function, you can call your functions successfully from inside main() with the default parameter (client1) or with any other TelegramClient as an argument.

The global scoped reference is necessary to avoid the namespace error name 'client1' is not defined. The lambda wrapper is necessary to avoid the error you would get from the value of the global reference not being set until main() runs (at compile-time when the default parameters are evaluated, it will be None and you'd get the error 'NoneType' object has no attribute 'showProofOfWorking').

Sign up to request clarification or add additional context in comments.

Comments

1

Functions are objects and can have attributes. I don't see this feature used much here on SO and have seen criticisms of their use - if I recall it obfuscates and interferes with introspection, but sometimes my memory is faulty.

If you want to be able to call a function without passing an argument but the value of the argument is not defined/calculated till after the function's definition you could make use of a function attribute like this.

def f():
    return 3 * f.x

def main():
    f.x = 6

main()
print(f())

You will have to decide whether this is more advantageous than just defining the function with parameter's - without default arguments - and just passing the argument when the function is called. At least for me there is not enough information in your question and [mre] to really understand whether this approach will satisfy your need.

Maybe you should have a module level variable, before the function definition

client1 = None
..
def get_statistic_with(client: TelegramClient = client1):
    ...

then reassign in the with statement.

1 Comment

caveat: In the second solution, using None will mess up the (static) type checking.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.