ฉันจะรับรายการคลาสทั้งหมดภายในโมดูลปัจจุบันใน Python ได้อย่างไร


301

ฉันเห็นตัวอย่างมากมายของผู้ที่ดึงคลาสทั้งหมดออกจากโมดูลซึ่งมักจะเป็นสิ่งที่ชอบ:

# foo.py
class Foo:
    pass

# test.py
import inspect
import foo

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

น่ากลัว

แต่ฉันไม่สามารถหาวิธีรับคลาสทั้งหมดจากโมดูลปัจจุบัน

# 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()

นี่อาจเป็นสิ่งที่ชัดเจนจริงๆ แต่ฉันไม่พบอะไรเลย ใครช่วยฉันออกได้บ้าง


2
มีPEPสำหรับคุณลักษณะเช่นนี้ แต่ถูกปฏิเสธ
Gary van der Merwe

มีอะไรผิดปกติกับการอ่านแหล่งที่มา"class"ใช่หรือไม่ ทำไมถึงไม่ได้ผล?
S.Lott

66
ฉันเดาว่าคำถามนั้นเกี่ยวกับการทำให้งานบางอย่างเป็นแบบอัตโนมัติดังนั้นจึงเป็นเรื่องสำคัญที่ต้องทำแบบเป็นโปรแกรม สมมุติว่าผู้ถามคิดว่าการทำด้วยตนเองโดยการอ่านซอร์สโค้ดด้วยสายตาของคุณอาจซ้ำซากผิดพลาดง่ายหรือใช้เวลานาน
Jonathan Hartley

คำตอบ:


386

ลองสิ่งนี้:

import sys
current_module = sys.modules[__name__]

ในบริบทของคุณ:

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

และยิ่งดีกว่า:

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

เพราะinspect.getmembers()ใช้คำกริยา


9
หากฉันนำเข้าคลาสในโมดูลนี้ที่ระดับโมดูล (เช่นfrom optparse import OptionParser) โมดูลเหล่านั้นจะรวมอยู่ในรายการพิมพ์ ฉันจะหลีกเลี่ยงสิ่งนั้นได้อย่างไร
Chris

5
@phasetwenty แทนที่จะเป็น inspect.isclass คุณสามารถมีสิ่งที่ชอบ:inspect.getmembers(sys.modules[__name__], lambda member: member.__module__ == __name__ and isnpect.isclass)
Nadia Alramli

1
แต่dict(inspect.getmembers(sys.modules[__name__])) == globals()เป็นเสมอTrueทำไมการนำเข้า?
kojiro

16
คำตอบของนาเดียเกือบจะถูกต้องแล้ว ดีกว่า: inspect.getmembers(sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__
William Budington

1
@JohnM isclassเพราะนาเดียลืมที่จะเรียก
Alex Hall

20

เกี่ยวกับอะไร

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

?


นี่คือสิ่งที่ฉันมักจะทำ คำตอบอื่น ๆ ดูเหมือนจะ "สะอาด" มากกว่านี้ไม่รู้เกี่ยวกับพวกเขา
Mizipzor

1
ดูเหมือนจะสะอาดมากสำหรับฉันโดยเฉพาะอย่างยิ่งถ้าคุณกรองisinstance(obj, types.ClassType)
kojiro

4
ฉันชอบคำตอบนี้ดีกว่าเพราะจะใช้งานได้แม้ว่าโมดูลปัจจุบันจะไม่ถูกวางใน sys.modules เช่นจากdocs.python.org/2/library/functions.html#execfile
Chris Smith

@ChrisSmith โดยเฉพาะฉันค้นพบในวันนี้ว่ามีผู้ debuggers บางคนเช่นpudbใช้งานโปรแกรมของคุณด้วยวิธีนี้ซึ่งส่งผลให้เกิดโค้ดโดยใช้การsys.modulesสุ่มในขณะที่ทำการดีบั๊ก globals()ดูเหมือนจะน่าเกลียดนิดหน่อย แต่ดูเหมือนว่าจะน่าเชื่อถือมากขึ้น
Soren Bjornstad

15

ฉันไม่ทราบว่ามีวิธีการ 'ถูกต้องหรือไม่' แต่ข้อมูลโค้ดของคุณอยู่ในเส้นทางที่ถูกต้อง: เพียงเพิ่มimport foofoo.py ทำinspect.getmembers(foo)และควรทำงานได้ดี


โอ้โหฉันคิดว่าสิ่งนี้จะสร้างการพึ่งพาแบบวงกลมหรือบางสิ่งบางอย่าง แต่ใช้งานได้!
mcccclean

เหตุผลที่คุณไม่ได้รับการอ้างอิงแบบวงกลมหรือการวนซ้ำการนำเข้าก็คือเมื่อคุณนำเข้าโมดูลมันจะถูกเพิ่มไปยังเนมสเปซส่วนกลาง เมื่อโมดูลที่อิมพอร์ตถูกเรียกใช้งานและเข้าสู่ 'import foo' โมดูลนั้นจะข้ามการอิมพอร์ตเนื่องจากโมดูลนั้นพร้อมใช้งานใน globals แล้ว หากคุณเรียกใช้ foo เป็น main (เป็นสคริปต์) โมดูลจะทำงานสองครั้งเพราะเมื่อคุณไปที่ 'import foo' mainจะอยู่ใน namespace ส่วนกลาง แต่ไม่ใช่ foo หลังจาก 'import foo' ทั้ง ' main ' และ 'foo' จะอยู่ในเนมสเปซ globals
กาลิ

10

ฉันสามารถรับทุกสิ่งที่ฉันต้องการจากสิ่งที่มีdirใน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)

แม้ว่ามันจะออกมาดูเหมือนกับลูกผม:

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']
        )
    ))

6
import pyclbr
print(pyclbr.readmodule(__name__).keys())

โปรดทราบว่าโมดูลเบราว์เซอร์คลาส Python ของ stdlib ใช้การวิเคราะห์แหล่งที่มาแบบคงที่ดังนั้นจึงใช้งานได้กับโมดูลที่สำรองข้อมูลโดย.pyไฟล์จริงเท่านั้น


4

หากคุณต้องการมีคลาสทั้งหมดที่อยู่ในโมดูลปัจจุบันคุณสามารถใช้สิ่งนี้:

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)

หากคุณใช้คำตอบของนาเดียและคุณกำลังนำเข้าคลาสอื่น ๆ ในโมดูลของคุณคลาสนั้นก็จะถูกนำเข้าด้วย

ดังนั้นที่ว่าทำไมจะถูกเพิ่มเข้าไปในกริยาที่ใช้ในmember.__module__ == __name__ is_class_memberคำสั่งนี้ตรวจสอบว่าคลาสเป็นของโมดูลจริงๆหรือไม่

เพรดิเคตเป็นฟังก์ชัน (callable) ที่ส่งคืนค่าบูลีน


3

โซลูชันอื่นที่ทำงานใน Python 2 และ 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()

สิ่งนี้ใช้ไม่ได้ใน 3.6.8 ฉันไม่ได้รับข้อผิดพลาดของโมดูล
Aviral Srivastava

3

นี่คือบรรทัดที่ฉันใช้เพื่อรับคลาสทั้งหมดที่กำหนดไว้ในโมดูลปัจจุบัน (เช่นไม่ได้นำเข้า) มันยาวไปหน่อยตาม PEP-8 แต่คุณสามารถเปลี่ยนได้ตามที่เห็นสมควร

import sys
import inspect

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

สิ่งนี้ให้รายชื่อคลาส หากคุณต้องการให้คลาสของวัตถุนั้นเพียงแค่เก็บ obj แทน

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

สิ่งนี้มีประโยชน์มากกว่าในประสบการณ์ของฉัน



0

ฉันคิดว่าคุณสามารถทำสิ่งนี้ได้

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())`

หากคุณต้องการชั้นเรียนของตัวเอง


0

ฉันมักจะพบว่าตัวเองกำลังเขียนโปรแกรมอรรถประโยชน์บรรทัดคำสั่งนั้นขัดแย้งแรกหมายถึงการอ้างถึงหนึ่งในชั้นเรียนที่แตกต่างกันจำนวนมาก ตัวอย่างเช่น./something.py feature command —-argumentsที่ไหนFeatureเป็นคลาสและcommandเป็นวิธีการในชั้นเรียนที่ นี่คือคลาสพื้นฐานที่ทำให้ง่าย

สมมติฐานคือคลาสฐานนี้อยู่ในไดเรกทอรีพร้อมกับคลาสย่อยทั้งหมด จากนั้นคุณสามารถโทรหาArgBaseClass(foo = bar).load_subclasses()ซึ่งจะส่งคืนพจนานุกรม ตัวอย่างเช่นหากไดเรกทอรีมีลักษณะเช่นนี้:

  • arg_base_class.py
  • feature.py

สมมติว่าfeature.pyการดำเนินการclass Feature(ArgBaseClass)แล้วภาวนาดังกล่าวข้างต้นจะกลับมาload_subclasses ( ) { 'feature' : <Feature object> }เดียวกันจะถูกส่งเข้าไปในชั้นเรียนkwargsfoo = barFeature

#!/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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.