I try to get the list of class from python file using python. After a few search, I get the code which I think it's work as follow
def get_class_from_file(class_obj, file, path='app', exclude=[]):
class_list = []
module = importlib.import_module(path + '.' + file)
for x in dir(module) :
app_cls = getattr( importlib.import_module(path + '.' + file), x )
try :
if app_cls and issubclass(app_cls, class_obj) and app_cls != class_obj and app_cls not in exclude:
class_list.append( (file, x) )
except TypeError :
pass
return class_list
However, I found out that the code don't get only the list of the class, but It still keep showing me the superclass of the class inside the file, here is example
file_1.py
class A:
pass
class B(A):
pass
file_2.py
class C(B):
pass
class D:
pass
when I call the function as
class_list = get_class_from_file(A, 'file_2')
I expect the result would be [C], but It return [C, B] as B is one of super class of C
Please help me fix this, I just want class inside the given file, not any superclass of them. By the way, I use exclude for fixing it at first, but It isn't give me a long run solution.
The problem is that imported modules are also found. You can check a class'
__module__ attribute to see if it originates from the current module or was imported into it.
You also have importlib.import_module(path + '.' + file) twice, I removed one of them. I renamed x to name.
def get_class_from_file(class_obj, file, path='app', exclude=[]):
class_list = []
module_path = path + '.' + file
module = importlib.import_module(module_path)
for name in dir(module) :
app_cls = getattr(module, name)
try:
if (issubclass(app_cls, class_obj) and
app_cls != class_obj and
app_cls not in exclude and
app_cls.__module__ == module_path):
class_list.append( (file, name) )
except TypeError:
# Not a class
pass
return class_list
Related
If I am importing a module from a 3rd party, but the syntax they use does not line up with mine, is there a good way to pep8 it?
Example: I need to use a 3rd party module that I cannot edit and their naming convention isn't so great.
Example:
thisIsABase_function(self,a,b)
I have some code that pepifies the name to pep8, but I was wondering how I can make the functions accessible by that new pep8 name?
def _pep8ify(name):
"""PEP8ify name"""
import re
if '.' in name:
name = name[name.rfind('.') + 1:]
if name[0].isdigit():
name = "level_" + name
name = name.replace(".", "_")
if '_' in name:
return name.lower()
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
Is there a way I can PEP8 these names on import?
You can use a context manager to automatically pep8ify the symbols from an imported module like:
Example:
with Pep8Importer():
import funky
Code:
class Pep8Importer(object):
#staticmethod
def _pep8ify(name):
"""PEP8ify name"""
import re
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
def __enter__(self):
# get list of current modules in namespace
self.orig_names = set(dir(sys.modules[__name__]))
def __exit__(self, exc_type, exc_val, exc_tb):
""" Pep8ify names in any new modules
Diff list of current module names in namespace.
pep8ify names at the first level in those modules
Ignore any other new names under the assumption that they
were imported/created with the name as desired.
"""
if exc_type is not None:
return
new_names = set(dir(sys.modules[__name__])) - self.orig_names
for module_name in (n for n in new_names if not n.startswith('_')):
module = sys.modules[module_name]
for name in dir(module):
pep8ified = self._pep8ify(name)
if pep8ified != name and not name.startswith('_'):
setattr(module, pep8ified, getattr(module, name))
print("In mModule: {}, added '{}' from '{}'".format(
module_name, pep8ified, name))
Test Code:
with Pep8Importer():
import funky
print(funky.thisIsABase_function)
print(funky.this_is_a_base_function)
funky.py
thisIsABase_function = 1
Results:
In module: funky, added 'this_is_a_base_function' from 'thisIsABase_function'
1
1
I think something like this does what you want:
# somemodule.py
def func_a():
print('hello a')
def func_b():
print('hello b')
# yourcode.py
import inspect
import importlib
def pepimports(the_module_name):
mymodule = importlib.import_module(the_module_name)
myfuncs = inspect.getmembers(f, inspect.isfunction)
for f in myfuncs:
setattr(mymodule, _pep8ify(f[1].__name__) , f[1])
return mymodule
mymodule = pepimports('some_module_name')
# you can now call the functions from mymodule
# (the original names still exist, so watch out for clashes)
mymodule.pepified_function()
It's a bit hackish, but I've tried it (python 3.5) and it seems to work (at least on a trivial example).
I am trying to build a program which allows the user to browse to a folder which contains python modules. Once the folder has been selected it will list all python files within that folder as well as all the classes and methods for each module. My question is, are there any way I can do this without opening each file and parsing for "def" or "class"? I noticed that there's a function called mro which returns the attribute of a class but that requires me to have access to that class through an import. So is there any way I can get the same result? Thank you in advance!
This is what I came up with using the AST module, it has exactly what I was looking for.
def fillClassList(file):
classList = []
className = None
mehotdName = None
fileName = "C:\Transcriber\Framework\ctetest\RegressionTest\GeneralTest\\" + file
fileObject = open(fileName,"r")
text = fileObject.read()
p = ast.parse(text)
node = ast.NodeVisitor()
for node in ast.walk(p):
if isinstance(node, ast.FunctionDef) or isinstance(node, ast.ClassDef):
if isinstance(node, ast.ClassDef):
className = node.name
else:
methodName = node.name
if className != None and methodName != None:
subList = (methodName , className)
classList.append(subList)
return classList
If you want to know the contents of the file, there's no way around looking into the file :)
Your choice comes down to whether you want to parse out the content-of-interest yourself, or if you want to let Python load the file and then ask it about what it found.
For a very simple Python file like testme.py below you can do something like this (warning: not for those with weak stomachs):
testme.py:
class Foo (object):
pass
def bar():
pass
analyze.py:
import os.path
files = ['testme.py']
for f in files:
print f
modname = os.path.splitext(f)[0]
exec('import ' + modname)
mod = eval(modname)
for symbol in dir(mod):
if symbol.startswith('__'):
continue
print ' ', symbol, type(eval(modname + '.' + symbol))
Output:
testme.py
Foo <type 'type'>
bar <type 'function'>
However, that's going to start to get pretty grotty when you expand it to deal with nested packages and modules and broken code and blah blah blah. Might be easier just to grep for class and/or def and go from there.
Have fun with it! I :heart: metaprogramming
Most of Python's implementation (parser included) is available in the stdlib, so by carefully reading the modules index you should find what you need. The first modules / packages that come to mind are importlib, inspect and ast but there surely other modules of interest.
I had to replace a lot of code in one of my modules, here is my way of getting classes and methods:
def listClass(file):
with open(file,"r") as f:
p = ast.parse(f.read())
# get all classes from the given python file.
classes = [c for c in ast.walk(p) if isinstance(c,ast.ClassDef)]
out = dict()
for x in classes:
out[x.name] = [fun.name for fun in ast.walk(x) if isinstance(fun,ast.FunctionDef)]
return out
Sample pprint output:
{'Alert': ['__init__',
'fg',
'fg',
'bg',
'bg',
'paintEvent',
'drawBG',
'drawAlert'],
'AlertMouse': ['__init__', 'paintEvent', 'mouseMoveEvent'],
'AlertPopup': ['__init__', 'mousePressEvent', 'keyPressEvent', 'systemInfo']}
Thanks, useful example for this first time ast user. Code above with the import, printed output, and without the 1 spelling error ;-)
import ast
classList = []
className = None
methodName = None
fileName = "C:\\fullPathToAPythonFile.py"
fileObject = open(fileName ,"r")
text = fileObject.read()
p = ast.parse(text)
node = ast.NodeVisitor()
for node in ast.walk(p):
if isinstance(node, ast.FunctionDef) or isinstance(node, ast.ClassDef):
if isinstance(node, ast.ClassDef):
className = node.name
else:
methodName = node.name
if className != None and methodName != None:
subList = (methodName , className)
classList.append(subList)
print("class: " + className + ", method: " + methodName)
I have a number of tests cases each defined in separate python files.
<file1.py>
class tc(testcase.tc):
....
<file2.py>
class tc(testcase.tc):
....
My main processing loop in RunTests.py has the following
class TestTable(wx.grid.PyGridTableBase):
def __init__(self):
wx.grid.PyGridTableBase.__init__(self)
self.cols = "Execute Number Name Status".split()
self.cases = []
package = tests
prefix = package.__name__ + "."
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__, prefix):
if 'tests.testcase' != modname: # skip the testcase.py file/module
print modname
module = __import__(modname, fromlist="dummy")
print module.tc()
self.cases.append( module.tc() )
I end up getting AttributeError: 'module' object has no attribute tc. My prints show the test case and tc instance
test.xxx
Not sure why....
Thanks
I found in saucelabs example.py file the following code snippet using the import new module.
import new
# .. snip ..
def on_platforms(platforms):
def decorator(base_class):
module = sys.modules[base_class.__module__].__dict__
for i, platform in enumerate(platforms):
d = dict(base_class.__dict__)
d['desired_capabilities'] = platform
name = "%s_%s" % (base_class.__name__, i + 1)
module[name] = new.classobj(name, (base_class,), d)
return decorator
However reading the help(new) shows that that module is deprecated. What is a non-depreciated way to do new.classobj(name, (base_class,), d)
The type keyword is a class. You can perform the above using
module[name] = type(name, (base_class,), d)
This will return a class of name name with the tuple parameters of the base class base_class, initializing the member objects with the dict d.
I am having some problem accessing class instances. I am calling the class from a procedure, name of instance is defined in some variable. I want the instance name to be created of that value and then want to access it, but when i access it is giving error. Can some one please help to resolve this issue.
class myclass:
def __init__(self,object):
self.name = object
def mydef():
global a1
b = "a1"
b = myclass(b)
mydef()
print a1.name
Second Problem:
In my actual script, I have to create a large number of such instances from this function (around 100). So defining their name as global would be painful, is there a way i could access those instances outside function without having to declare them as global.
Modification:
class myclass:
def __init__(self,object,typename):
self.name = object
self.typeid = typename
def mydef():
file_han = open(file,"r")
while True:
line = file_han.readline()
if not line:
break
start = line.find('"')
end = line.find('"',start+1)
string_f = line[start+1:end]
myclass(string_f,'a11')
mydef(file)
print def.name
print def.typeid
File Contents are :
a11 "def"
a11 "ghi"
a11 "eff"
Here's how I'd do it. I don't know why you're messing around with globals, if you'd care to explain, I'll update my answer.
class Myclass(object):
def __init__(self, name):
self.name = name
def mydef():
return Myclass("a1")
a1 = mydef()
print a1.name
Gather your instances in a list:
instances = []
for x in range(1000):
instances.append(Myclass("Instance {0}".format(x)))
print instance[42].name
Note the changes:
Class names should be capitalized
Use object as the base class of your classes (since python 2.2, but no longer necessary in 3.x)
Don't shadow the built-in object with your parameter name
Just use the string "a1" directly as a parameter instead of assigning it to a variable
Return something from the function instead of passing the result by global variable
RE: Comment
You haven't said anything about the format of these files, so I'll just give an example where the file to be read contains one class name per line, and nothing else:
def mydef(filename):
ret = []
with open(filename) as f:
for line in f:
# Call `strip` on line to remove newline and surrounding whitespace
ret.append(Myclass(line.strip()))
return ret
So if you have several files and wish to add all your instances from all your files to a large list, do it like this:
instances = []
for filename in ["myfile1", "myfile2", "myfile3"]:
instances.extend(mydef(filename))
RE: OP Edit
def mydef(filename):
ret = []
with open(filename, "r") as file_han:
for line in file_han:
string_f = line.split('"')[1]
ret.append(Myclass(string_f))
return ret
i = mydef("name_of_file")
RE: Comment
Oh, you want to access them by name. Then return a dict instead:
def mydef(filename):
ret = {}
with open(filename, "r") as file_han:
for line in file_han:
string_f = line.split('"')[1]
ret[string_f] = Myclass(string_f)
return ret
i = mydef("name_of_file")
print i["ghi"].name # should print "ghi"
RE: Comment
If I understand you correctly, you want to have it both ways -- index by both line number and name. Well then why don't you return both a list and a dictionary?
def mydef(filename):
d = {}
L = []
with open(filename, "r") as file_han:
for line in file_han:
string_f = line.split('"')[1]
instance = Myclass(string_f)
d[string_f] = instance
L.append(instance)
return L, d
L, d = mydef("name_of_file")
print d["ghi"].name
print L[3]
print L.index(d["ghi"])
You could use class as repository for your instances, for example
class Named(object):
def __init__(self,name):
self.name = name
def __new__(cls,name):
instance = super(type,cls).__new__(cls,name)
setattr(cls,name,instance)
return instance
def __repr__(self):
return 'Named[%s]'%self.name
Named('hello')
Named('x123')
Named('this is not valid attribute name, but also working')
print(Named.hello,Named.x123,getattr(Named,'this is not valid attribute name, but also working'))