Bagaimana saya bisa mendapatkan daftar semua kelas dalam modul saat ini dengan Python?

301

Saya telah melihat banyak contoh orang yang mengekstraksi semua kelas dari sebuah modul, biasanya sekitar seperti:

# foo.py
class Foo:
    pass

# test.py
import inspect
import foo

for name, obj in inspect.getmembers(foo):
    if inspect.isclass(obj):
        print obj

Luar biasa.

Tapi saya tidak tahu bagaimana cara mendapatkan semua kelas dari modul saat ini .

# foo.py
import inspect

class Foo:
    pass

def print_classes():
    for name, obj in inspect.getmembers(???): # what do I do here?
        if inspect.isclass(obj):
            print obj

# test.py
import foo

foo.print_classes()

Ini mungkin sesuatu yang sangat jelas, tetapi saya belum dapat menemukan apa pun. Adakah yang bisa membantu saya?

mcccclean
sumber
2
Ada PEP untuk fitur seperti ini, tetapi ditolak.
Gary van der Merwe
Untuk apa membaca sumbernya "class"? Mengapa itu tidak berhasil?
S.Lott
67
Saya kira pertanyaannya adalah tentang ingin mengotomatiskan beberapa tugas, jadi penting dilakukan secara terprogram. Mungkin si penanya berpikir bahwa melakukannya secara manual, dengan membaca kode sumber dengan mata Anda, mungkin berulang, rawan kesalahan atau memakan waktu.
Jonathan Hartley

Jawaban:

386

Coba ini:

import sys
current_module = sys.modules[__name__]

Dalam konteks Anda:

import sys, inspect
def print_classes():
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj):
            print(obj)

Dan bahkan lebih baik:

clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass)

Karena inspect.getmembers()butuh predikat.

Nadia Alramli
sumber
9
Jika saya mengimpor kelas dalam modul ini di tingkat modul (yaitu, from optparse import OptionParser) modul-modul tersebut termasuk dalam daftar cetak. Bagaimana saya bisa menghindarinya?
Chris
5
@phasetwenty, bukannya inspect.isclass Anda dapat memiliki sesuatu seperti:inspect.getmembers(sys.modules[__name__], lambda member: member.__module__ == __name__ and isnpect.isclass)
Nadia Alramli
1
tetapi dict(inspect.getmembers(sys.modules[__name__])) == globals()selalu True, jadi mengapa impor?
kojiro
16
Jawaban Nadia hampir benar. Lebih baik: inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__
William Budington
1
@JohnM. karena Nadia lupa menelpon isclass.
Alex Hall
20

Bagaimana dengan

g = globals().copy()
for name, obj in g.iteritems():

?

Krab
sumber
Inilah yang biasanya saya lakukan. Jawaban lain tampaknya jauh lebih "bersih", tidak tahu tentang mereka.
Mizipzor
1
Tampaknya banyak bersih bagi saya, terutama jika Anda memfilterisinstance(obj, types.ClassType)
kojiro
4
Saya suka jawaban ini lebih baik karena akan berfungsi walaupun modul saat ini belum dimasukkan ke dalam sys.modules, misalnya dari docs.python.org/2/library/functions.html#execfile
Chris Smith
@ ChrisSmith Secara khusus, saya menemukan hari ini bahwa beberapa debugger seperti pudbmenjalankan program Anda dengan cara ini, yang menghasilkan kode menggunakan sys.modulesmelanggar secara acak saat debugging. globals()tampaknya sedikit jelek, tetapi tampaknya jauh lebih dapat diandalkan.
Soren Bjornstad
15

Saya tidak tahu apakah ada cara yang 'tepat' untuk melakukannya, tetapi cuplikan Anda ada di jalur yang benar: tambahkan saja import fooke foo.py, lakukan inspect.getmembers(foo), dan itu akan berfungsi dengan baik.

int3
sumber
Wah, saya akan mengira ini akan menciptakan ketergantungan melingkar atau sesuatu, tetapi berhasil!
mcccclean
Alasan Anda tidak mendapatkan dependensi melingkar atau loop impor adalah bahwa setelah Anda mengimpor modul itu ditambahkan ke namespace global. Ketika modul yang diimpor dieksekusi dan mendapat 'import foo', modul ini mengabaikan impor karena modul tersebut sudah tersedia di global. Jika Anda menjalankan foo sebagai main (sebagai skrip) modul ini sebenarnya dijalankan dua kali karena ketika Anda masuk ke 'import foo' main akan berada di namespace global tetapi tidak foo. Setelah 'impor foo' baik ' utama ' dan 'foo' akan berada di namespace global.
Galinden
11

Saya bisa mendapatkan semua yang saya butuhkan dari dirbuilt in plus getattr.

# Works on pretty much everything, but be mindful that 
# you get lists of strings back

print dir(myproject)
print dir(myproject.mymodule)
print dir(myproject.mymodule.myfile)
print dir(myproject.mymodule.myfile.myclass)

# But, the string names can be resolved with getattr, (as seen below)

Meskipun, itu tampak seperti bola rambut:

def list_supported_platforms():
    """
        List supported platforms (to match sys.platform)

        @Retirms:
            list str: platform names
    """
    return list(itertools.chain(
        *list(
            # Get the class's constant
            getattr(
                # Get the module's first class, which we wrote
                getattr(
                    # Get the module
                    getattr(platforms, item),
                    dir(
                        getattr(platforms, item)
                    )[0]
                ),
                'SYS_PLATFORMS'
            )
            # For each include in platforms/__init__.py 
            for item in dir(platforms)
            # Ignore magic, ourselves (index.py) and a base class.
            if not item.startswith('__') and item not in ['index', 'base']
        )
    ))
ThorSummoner
sumber
6
import pyclbr
print(pyclbr.readmodule(__name__).keys())

Perhatikan bahwa modul browser kelas Python stdlib menggunakan analisis sumber statis, sehingga hanya berfungsi untuk modul yang didukung oleh .pyfile nyata .

ncoghlan
sumber
4

Jika Anda ingin memiliki semua kelas, yang termasuk dalam modul saat ini, Anda dapat menggunakan ini:

import sys, inspect
def print_classes():
    is_class_member = lambda member: inspect.isclass(member) and member.__module__ == __name__
    clsmembers = inspect.getmembers(sys.modules[__name__], is_class_member)

Jika Anda menggunakan jawaban Nadia dan Anda mengimpor kelas lain pada modul Anda, kelas itu akan diimpor juga.

Jadi itu sebabnya member.__module__ == __name__ditambahkan ke predikat yang digunakan padais_class_member . Pernyataan ini memeriksa apakah kelas benar-benar milik modul.

Predikat adalah fungsi (bisa dipanggil), yang mengembalikan nilai boolean.

Benjy Malca
sumber
3

Solusi lain yang bekerja di Python 2 dan 3:

#foo.py
import sys

class Foo(object):
    pass

def print_classes():
    current_module = sys.modules[__name__]
    for key in dir(current_module):
        if isinstance( getattr(current_module, key), type ):
            print(key)

# test.py
import foo
foo.print_classes()
Florian
sumber
Ini tidak berfungsi di 3.6.8. Saya tidak mendapatkan kesalahan modul.
Aviral Srivastava
3

Ini adalah baris yang saya gunakan untuk mendapatkan semua kelas yang telah didefinisikan dalam modul saat ini (yaitu tidak diimpor). Agak panjang menurut PEP-8 tetapi Anda bisa mengubahnya sesuai keinginan Anda.

import sys
import inspect

classes = [name for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass) 
          if obj.__module__ is __name__]

Ini memberi Anda daftar nama kelas. Jika Anda ingin objek kelas itu sendiri tetap saja obj.

classes = [obj for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass)
          if obj.__module__ is __name__]

Ini lebih bermanfaat dalam pengalaman saya.

Austin Mackillop
sumber
1
import Foo 
dir(Foo)

import collections
dir(collections)
Avinash Koneru
sumber
0

Saya pikir Anda dapat melakukan sesuatu seperti ini.

class custom(object):
    __custom__ = True
class Alpha(custom):
    something = 3
def GetClasses():
    return [x for x in globals() if hasattr(globals()[str(x)], '__custom__')]
print(GetClasses())`

jika Anda membutuhkan kelas sendiri

Rain0Ash
sumber
0

Saya sering menemukan diri saya menulis utilitas baris perintah di mana argumen pertama dimaksudkan untuk merujuk ke salah satu dari banyak kelas yang berbeda. Misalnya ./something.py feature command —-arguments, di mana Featurekelas dancommand metode pada kelas itu. Inilah kelas dasar yang membuatnya mudah.

Asumsinya adalah bahwa kelas dasar ini berada di direktori bersama semua subkelasnya. Anda kemudian dapat memanggil ArgBaseClass(foo = bar).load_subclasses()yang akan mengembalikan kamus. Misalnya, jika direktori terlihat seperti ini:

  • arg_base_class.py
  • feature.py

Dengan asumsi feature.pymengimplementasikan class Feature(ArgBaseClass), maka doa di atas load_subclassesakan kembali { 'feature' : <Feature object> }. Hal yang sama kwargs( foo = bar) akan diteruskan ke Featurekelas.

#!/usr/bin/env python3
import os, pkgutil, importlib, inspect

class ArgBaseClass():
    # Assign all keyword arguments as properties on self, and keep the kwargs for later.
    def __init__(self, **kwargs):
        self._kwargs = kwargs
        for (k, v) in kwargs.items():
            setattr(self, k, v)
        ms = inspect.getmembers(self, predicate=inspect.ismethod)
        self.methods = dict([(n, m) for (n, m) in ms if not n.startswith('_')])

    # Add the names of the methods to a parser object.
    def _parse_arguments(self, parser):
        parser.add_argument('method', choices=list(self.methods))
        return parser

    # Instantiate one of each of the subclasses of this class.
    def load_subclasses(self):
        module_dir = os.path.dirname(__file__)
        module_name = os.path.basename(os.path.normpath(module_dir))
        parent_class = self.__class__
        modules = {}
        # Load all the modules it the package:
        for (module_loader, name, ispkg) in pkgutil.iter_modules([module_dir]):
            modules[name] = importlib.import_module('.' + name, module_name)

        # Instantiate one of each class, passing the keyword arguments.
        ret = {}
        for cls in parent_class.__subclasses__():
            path = cls.__module__.split('.')
            ret[path[-1]] = cls(**self._kwargs)
        return ret
Zane Claes
sumber