Make instance methods global in module? - python

I create a lib which can be imported and used as is, and there is also a start script, which creates a single instance of the main class for use within the script:
# read config, init stuff, and then create an instance
r = RepoClient(config)
Now, the start script accepts a 'command' argument, and as of now, it must be invoke like:
repo config.json -c 'r.ls()'
i.e. the 'r' variable must be used.
I would like to be able to drop the 'r' variable. For that, the start script, somehow, needs the ls function. I can do it by putting the following in the script, after the r instance is created:
ls = r.ls
etc. for all the commands the RepoClient class supports.
Is there anything automatic? The code below doesn't work, of course, but you get the idea:
from r import *
What I can think of is annotating methods with a custom #command annotation, iterating over all the methods and checking for it and if found, setting it as a script global, but hopefully the batteries do support something like this already ;d
EDIT: for now, the command passed as the last argument is run the following way:
exec(sys.argv[2])

No, there is no way to do that "automatically". However, you don't actually need to copy the items into the global namespace. You can pass a namespace to exec to use. You could give your RepoClient a __getitem__ method, allowing it to act as a namespace. Here's a simple example:
class Foo(object):
def blah(self):
print("Blah!")
def __getitem__(self, attr):
return getattr(self, attr)
f = Foo()
exec('blah()', globals(), f)
It outputs Blah!.

Related

How to access value in a function as a global?

Note: updated my original question
I have this code part in a script and need to access co_create value outside the module in the script.
from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
def __init__(self):
super(CallbackModule, self).__init__()
def v2_playbook_on_play_start(self, play):
self.play = play
extra_vars = ""
vm = play.get_variable_manager()
co_create = vm.extra_vars["co_create"]
The way you have formatted print(co_create) on the final line in your snippet, it appears as the last line of the class definition for CallbackModule. When the class constructor get's to that point it is rightly complaining that the variable is not defined as there is neither a class-level variable in your example called co_create nor a reference to any global variable called co_create available at the module level. You have two options: (1.) define a module-level variable in the peer context to the class' definition, before the class is defined, called 'co_create' like:
co_create = None
class CallbackModule(CallbackBase):
...
if __name__ == '__main__':
play = ...
obj = CallbackModule()
obj.v2_playbook_on_play_start(play)
print(co_create) # no longer None
This will become the global variable you desire and should not require code change to any of your other code. (2.) similar to what you defined in the instance method v2_playbook_on_play_start access co_create with the global keyword at the class level before you print it. Option (1.) is preferred. You should try to avoid using global keyword unless you have to simply because it will require consistency across rest of module, hence more global keywords and more opportunities for error.
I got my answer: it's not possible what I originally wanted to achieve with the ansible module: to get the variable retrieved from the module into the main block in the script, because the 'play' is called directly from ansible run, so no way I can specify it in the main block.
So I did make it work to push the return value from the main block to the module and the module to do the task instead.
Since skullgoblet1089 gave me a good direction, I will mark his answer good.

Dynamically Defining a Dummy Decorator

I am using a nice tool called https://github.com/rkern/line_profiler
To use it, you need to put a decorator #profile at multiple places in the script to indicate which functions should be profiled. Then you execute the script via
kernprof -l script_to_profile.py
Obviously, when running the script by itself via python script_to_profile.py, the decorator is not defined and hence the script crashes.
I know how to define an identity decorator and I can pass a flag from the command line and define it in the main script depending on how the flag is set. However, I don't know how to pass the decorator definition (or the flag) to modules I load so they don't crash at the moment they are loaded. Any ideas?
def profile(func):
return func
A very simple way would be to check if something named profile exists, and if it doesn't, then define it to be your identity decorator. Something like this.
try:
profile
except NameError:
def profile(func):
return func
You could go a little further and make sure it's something callable — probably not necessary:
import typing
try:
profile
except NameError:
profile = None
if not isinstance(profile, typing.Callable):
def profile(func):
return func

Calling functions / class methods inside a for loop

I'm working on a some classes, and for the testing process it would be very useful to be able to run the class methods in a for loop. I'm adding methods and changing their names, and I want this to automatically change in the file where I run the class for testing.
I use the function below to get a list of the methods I need to run automatically (there are some other conditional statements I deleted for the example to make sure that I only run certain methods that require testing and which only have self as an argument)
def get_class_methods(class_to_get_methods_from):
import inspect
methods = []
for name, type in (inspect.getmembers(class_to_get_methods_from)):
if 'method' in str(type) and str(name).startswith('_') == False:
methods.append(name)
return methods
Is it possible to use the returned list 'methods' to run the class methods in a for loop?
Or is there any other way to make sure i can run my class methods in my testingrunning file without having to alter or add things i changed in the class?
Thanks!
Looks like you want getattr(object, name[, default]):
class Foo(object):
def bar(self):
print("bar({})".format(self))
f = Foo()
method = getattr(f, "bar")
method()
As a side note : I'm not sure that dynamically generating lists of methods to test is such a good idea (looks rather like an antipattern to me) - now it's hard to tell without the whole project's context so take this remarks with the required grain of salt ;)

Dynamically name a function as the script, Python

Is it possible to dynamically name a function in a script as the name of the script? For example, if the script is called foo.py, is it possible to dynamically name a function in foo.py as foo? The reason I'm asking is that I import functions from several scripts and the naming convention is function_to_import = script - and to avoid any misspelling in the functions I'd like it to be dynamic. Thanks!
Yes, you can do something like
def main():
pass
globals()[__name__] = main
# if you no longer want the function to exist under its original name
del main
Messing with globals() is not generally recommended. I think it would make for clearer code to just bite the bullet and manually type out the name you need, but this is probably not the worst thing to do.
Note that this only changes the name that the function can be accessed with. It doesn't change the name of the underlying function object that you're accessing. If your code relies on that name, then you will have to do something more complicated.
There are multiple ways to assign a new name to a function, please note this would not change the name of the function, but the new name would also be pointing to that function.
Example 1 - While importing you can use as keyword to assign a new name, and then use it in the script using the new name
from foo import func as foo
foo()
Example 2 - You can assign the function to a new variable (a new name) and then use the new name to call it -
>>> def func(a):
... print("Hello")
...
>>> foo = func
>>> foo(1)
Hello
There may be more ways to do this.
You can use __file__ to get the filename and then assign the function to that file.
def my_function():
print "Hello, World!"
exec(__file__.split('.')[0] + " = my_function")
If you add this to your file, it will dynamically name the function my_function as the name of your file.

How to create cross-module, on-thy-fly variable name in python?

What I am trying to do, is creating a module, with a class; and a function, which is an interface of that class; and a variable name on-the-fly in this function, which is pointing to an instance of that class. This function and the class itself should be in a separate module, and their usage should be in a different python file.
I think, it's much easier to understand what I am trying to do, when you are looking at my code:
This is the first.py:
class FirstClass:
def setID(self, _id):
self.id = _id
def func(self):
pass
# An 'interface' for FirstClass
def fst(ID):
globals()['%s' % ID] = FirstClass(ID)
return globals()['%s' % ID]
Now, if I'm calling fst('some_text') right in first.py, the result is pretty much what I dreamed of, because later on, any time I write some_text.func(), it will call the func(), because some_text is pointing to an instance of FirstClass.
But, when the second.py is something like this:
from first import fst
fst('sample_name')
sample_name.func()
Then the answer from python is going to be like this:
NameError: name 'sample_name' is not defined.
Which is somewhat reasonable.. So my question is: is there a "prettier" method or a completely different one to do this? Or do I have to change something small in my code to get this done?
Thank you!
Don't set it as a global in the function. Instead, just return the new instance from the function and set the global to that return value:
def fst(ID):
return FirstClass(ID)
then in second.py:
sample_name = fst('sample_name')
where, if inside a function, you declare sample_name a global.
The globals() method only ever returns the globals of the module in which you call it. It'll never return the globals of whatever is calling the function. If you feel you need to have access to those globals, rethink your code, you rarely, if ever, need to alter the globals of whatever is calling your function.
If you are absolutely certain you need access to the caller globals, you need to start hacking with stack frames:
# retrieve caller globals
import sys
caller_globals = sys._getframe(1).f_globals
But, as the documentation of sys._getframe() states:
CPython implementation detail: This function should be used for internal and specialized purposes only. It is not guaranteed to exist in all implementations of Python.

Categories