Accessing a method using getattr - python

When creating a new class instance, I'm trying to call a method in a different class however can't get it to work. Here's what I have:
class DataProject(object):
def __init__(self, name=none,input_file=None,datamode=None,comments=None,readnow=True):
..............
# here's where I'm trying to call the method in the other class
if type(input_file) == str:
self.input_file_format = self.input_file.split(".")[-1]
if readnow:
getattr(Analysis(),'read_'+self.input_file_format)(self,input_file)
class Analysis(object):
def __init__(self):
pass # nothing happens here atm
def read_xlsx(self,parent,input_file):
"""Method to parse xlsx files and dump them into a DataFrame"""
xl = pd.ExcelFile(input_file)
for s in sheet_names:
parent.data[s]=xl.parse(s)
I'm getting a NameError: global name 'read_xlsx' is not defined when I run this with afile.xlxs as input which made me think that I just discovered a massive hole in my Python knowledge (not that there aren't many but they tend to be hard to see, sort of like big forests...).
I would have thought that getattr(Analysis(), ... ) would access the global name space in which it would find the Analysis class and its methods. And in fact print(globals().keys()) shows that Analysis is part of this:
['plt', 'mlab', '__builtins__', '__file__', 'pylab', 'DataProject', 'matplotlib', '__package__', 'W32', 'Helpers', 'time', 'pd', 'pyplot', 'np', '__name__', 'dt', 'Analysis', '__doc__']
What am I missing here?
EDIT:
The full traceback is:
Traceback (most recent call last):
File "C:\MPython\dataAnalysis\dataAnalysis.py", line 101, in <module>
a=DataProject(input_file='C:\\MPython\\dataAnalysis\\EnergyAnalysis\\afile.xlxs',readnow=True)
File "C:\MPython\dataAnalysis\dataAnalysis.py", line 73, in __init__
getattr(Analysis(),'read_'+self.input_file_format)(self,input_file)
File "C:\MPython\dataAnalysis\dataAnalysis.py", line 90, in read_xls
read_xlsx(input_file)
NameError: global name 'read_xlsx' is not defined
My main call is:
if __name__=="__main__":
a=DataProject(input_file='C:\\MPython\\dataAnalysis\\EnergyAnalysis\\afile.xlx',readnow=True)

From the full traceback, it appears that your DataProject class is calling (successfully) the Analysys.read_xls method, which in turn is trying to call read_xlsx. However, it's calling it as a global function, not as a method.
Probably you just need to replace the code on line 90, turning read_xlsx(input_file) into self.read_xlsx(input_file), though you might need to pass an extra parameter for the parent DataProject instance too.

getattr() works as you describe it in both Python2.x and Python3.x. The bug must be somewhere else.
This modification of your code (none of the core logic is changed) works fine for instance:
class DataProject(object):
def __init__(self, name="myname",input_file="xlsx",datamode=None,comments=None,readnow=True):
if type(input_file) == str:
self.input_file_format = input_file.split(".")[-1]
if readnow:
getattr(Analysis(),'read_'+self.input_file_format)(self,input_file)
class Analysis(object):
def __init__(self):
pass # nothing happens here atm
def read_xlsx(self,parent,input_file):
"""Method to parse xlsx files and dumpt them into a DataFrame"""
print("hello")
a=DataProject()
Output is:
$ python3 testfn.py
hello
Why using getattr() in this way is usually a bad idea
The way you are using getattr forces a naming convention on your methods (read_someformat). The naming of your methods should not be a core part of your program's logic. - You should always be able to change a function's name at every call and definition of that function and leave behaviour of the program intact.
If a file format needs to be handled by a specific method this logic should be delegated to some unit (e.g a function) with responsibility for this. One way (there are others) of doing this is to have a function which takes the input and decides which function needs to handle it:
def read_file(self,file,format):
if format == `xls`:
self.read_xls(file)
if format == `csv`:
self.read_csv(file)
The above snippet does have its issues too (a better way to do it would be the chain of responsibility pattern for example) but it will be fine for small scripts and is much nicer.

Related

How to run custom code in IntEnum class body

I'm trying to generate the values that will go into a custom enum instead of using literals:
from enum import IntEnum
class Test(IntEnum):
for i in range(3):
locals()['ABC'[i]] = i
del i
My desired output is three attributes, named A, B, C, with values 0, 1, 2, respectively. This is based on two expectations that I've come to take for granted about python:
The class body will run in an isolated namespace before anything else
locals during that run will refer to said isolated namespace
Once the body is done executing, I would expect the result to be not much different than calling IntEnum('Test', [('A', 0), ('B', 1), ('C', 2)]) (which works just fine BTW).
Instead, I get an error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in Test
File "/usr/lib/python3.8/enum.py", line 95, in __setitem__
raise TypeError('Attempted to reuse key: %r' % key)
TypeError: Attempted to reuse key: 'i'
If I try doing the same with class Test: instead of class Test(IntEnum):, it works as expected. The traceback is showing the problem to be happening in enum.py. This contradicts my assumptions about how things work.
What is going on with this code, and how to I create attributes in the local namespace of the class body before IntEnum can get to them?
Background The reason that I'm trying to create the enum this way is that the "real" values are a more complex tuple, and there is a __new__ method defined to parse the tuple and assign some attributes to the individual enum objects. All that does not seem to be relevant to figuring out what is happening with the error and fixing it.
First, an explanation of what is happening. Before executing the class body, the metaclass's __prepare__ method is used to create the namespace. Normally, this is just a dict. However, enum.EnumType uses a enum._EnumDict class, which specifically prevents duplicate names from being added to the namespace. While this does not alter how the code in the class body is run, it does alter the namespace into which that code places names.
There are a couple of exceptions to the duplicate prevention, which offer potential solutions. First, the proper solution is to use the _ignore_ sunder attribute. If it gets set first, the variable i can be used normally, and will not appear in the final class:
class Test(IntEnum):
_ignore_ = ['i']
for i in range(3):
locals()['ABC'[i]] = i
Another, much hackier method is to use a dunder name, which will be ignored by the metaclass:
class Test(IntEnum):
for __i__ in range(3):
locals()['ABC'[__i__]] = __i__
del __i__
While this solution is functional, it uses dunders, which are nominally reserved by the language, and an undocumented feature, both of which are bad.

Using Functions from Other .py Files

So I'm very new to Python and programming in general. I've run into an issue with my text game. I'm trying to create a function (pretty sure 'def (name) is a function, right?)in a .py called 'locationMenu' and use it in my main game file. Here is the start of my locationMenu.py (the rest is simply prints and if/elif/else statements.
from main import Engine, Hallway, Canteen, Bedroom, Room
class locationsMenu():
def locationMenu(self):
and here is the place in my main game file where I am trying to use the 'locationMenu' function that I just created.
locationMenu()
That is just one line after I made a choice with input, etc. but now I would like to skip to the locationMenu that is in the other file.
Any help much appreciated. Please don't use "big words" because as I said I'm still very new to programming in general.
locationMenu() is an instance method of the locationsMenu() class, so you'd need to create an instance and call the method on it, something like:
from locationMenu import locationsMenu
my_menu = locationsMenu() # Create a new object
my_menu.locationMenu() # Call its instance method
If you were to just try locationsMenu.locationMenu() then you'd get something like this error:
Traceback (most recent call last):
File "./prog.py", line 3, in <module>
locationsMenu.locationMenu()
TypeError: locationMenu() missing 1 required positional argument: 'self'
because locationMenu() is not a class method, and you're trying to call it in the absence of a locationsMenu object.
If you don't want it inside a class at all, then make it a regular function, and do:
locationMenu.py:
from main import Engine, Hallway, Canteen, Bedroom, Room
def locationMenu():
print("In function locationMenu()")
prog.py:
from locationMenu import locationMenu
locationMenu()

Create Python object from different .py file

In one file I create a class called Robot, but when I try to create an object of that class in another file it says:'module' object has no attribute 'Robot'
main.py
import robot as rob
robot=rob.Robot()
robot.py
class Robot(object):
def __init__(self):
return 0
def otherFunctions():
return 0
And it says: 'module' object has no attribute 'Robot'.
Where I am making a mistake?
The way your code is written is correct (barring removal you've presumably made for conciseness)
When you import, Python checks sys.path for importing locations, and imports the first robot it can find.
A couple ways to solve this:
import robot
print robot.__file__
in robot.py
print("hello!")
import sys
sys.path.insert('/path/to/correct/robot/')
import robot
It seems like the syntax in your robot.py file is not correct. You can correct the errors in the most direct way by changing your robot.py file to look like this:
class Robot(object):
def __init__(self):
pass
def other_functions(self):
pass
Note that I used snake casing for the other_functions function. Don't use camelCasing in Python. It's not idiomatic. Also, I added a self argument to other_functions so you won't get a TypeError if you try to invoke it off of a Robot instance.
Also, unless your code is truly as simple as you present it, the error might be coming from a circular import. Make sure you're not trying to import the two modules from each other before they've had a chance to fully execute.

How to run "exec" from the global scope in python

I have a Class. In that class I have a function.
In the function, I have a string variable that holds definitions of several python functions.
I would like from the function to create the functions that are defined in the variable, such that they will be created in the global scope.
After this operation, I would like to be able to call to the new function from the global scope.
For example:
class MyClass:
def create_functions():
functions_to_create = """
def glob1():
return "G1"
def glob2():
return "G2"
"""
# ----> HERE IS THE MISSING PART, LIKE RUNNING exec in the global scope <----
# The following function should work:
def other_function_in_global_scope():
print "glob1: %s, glob2: %s" % (glob1(), glob2())
What should be in the MISSING PART?
Thanks in advance!!!
In python the overrides can monkey-patch anything anytime, but if you just evaluate a bit of code in global namespace, the risk of inadvertent symbol conflict. I'd suggest instead the customer would provide a module and your code would call functions in it if they were defined there (and default implementations otherwise).
That said, documentation suggests:
exec(functions_to_create, globals())
Several things first. What is your reason to creating a function to create other functions? What are you trying to do? There might be a better way. Also here is another way to so called create function that doesn't involve playing around with exec.
>>> def create_functions():
... global glob1
... def glob1():
... return "G1"
...
>>> glob1()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'glob1' is not defined
>>> create_functions()
>>> glob1()
'G1'
>>>
Edit
Injecting source code without exec (THIS IS NOT A GOOD IDEA AT ALL)
Have you customer submit their code then just do a custom import
Customer Submit Code
Save that code as say custom.py
In your code that you want to let the customer inject into do something like the following
import os
if os.path.exists("custom.py"):
import custom
custom.inject()
That way they can give you their code you call inject and they can change things.

Run multiple functions in a for loop python

Here is the start of my program. I want a lot of the functions to be inside the for loop as seen in the 3rd function here. How do I go about this?
#!/usr/bin/env python
from rdflib import URIRef, Graph
from StringIO import StringIO
import subprocess as sub
class Wordnet():
def __init__(self, graph):
graph = Graph()
def process_file(self, file):
file = open("new_2.txt", "r")
return file
def line_for_loop(self, file):
for line in file:
def split_pointer_part(self, before_at, after_at, line):
before_at, after_at = line.split('#', 1)
return before_at, after_at
def split_word_part(self, word_part, line):
word_part = line.split()
return word_part
Is it just a matter of indenting everything else in the for loop or is it when the function are called that the loop has to be defined?
How does one go about calling multiple functions as part of a program? I am new to python and i don't really know.
There's no program here. Classes by themselves don't do anything. You need to instantiate the class, then call one of its methods (which is the correct term for what you seem to be calling "processes"). So, at the end of this file, you might do:
wordnet = Wordnet()
my_file = wordnet.process_file()
wordnet.line_for_loop(my_file)
Inside one method, you can call another: so for your loop, you would do:
def line_for_loop(self, file):
for line in file:
self.my_method_1()
self.my_method_2()
There are some other issues with your code. For example, in the __init__ method, you define a graph local variable, but never do anything with it, so it is not stored anywhere. You need to store variables on self for them to become instance properties:
def __init__(self):
self.graph = Graph()
Also, you seem to be confused about when to pass parameters to functions. Twice (in __init__ and process_file) you accept a parameter, then override it inside the method with a local variable. If you're defining the variable in the function, you shouldn't pass it as a parameter.
Note that, as I've had occasion to say before, Python is not Java, and doesn't always require classes. In this case, the class is not contributing anything to the program, other than as a holder for methods. In Python, you would normally just use functions inside a module for that.
Process isn't the proper term to use. Those are better known as functions or methods. As far as Python loops go, indentation is important. You do need to indent.
def line_for_loop(self, file):
for line in file:
process_file("example_file_name.txt")
split_pointer_part(0, 10, "some test string")
You should make the function calls from inside the loop. The example code above may not be the exact solution for you code, but it should be sufficient enough to answer your question.

Categories

Resources