Câu trả lời:
Các lớp kiểu mới (tức là lớp con từ object
, được mặc định trong Python 3) có một __subclasses__
phương thức trả về các lớp con:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Dưới đây là tên của các lớp con:
print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
Dưới đây là các lớp con:
print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Xác nhận rằng các lớp con thực sự liệt kê Foo
là cơ sở của chúng:
for cls in Foo.__subclasses__():
print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Lưu ý nếu bạn muốn các lớp con, bạn sẽ phải lặp lại:
def all_subclasses(cls):
return set(cls.__subclasses__()).union(
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
print(all_subclasses(Foo))
# {<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>}
Lưu ý rằng nếu định nghĩa lớp của lớp con chưa được thực thi - ví dụ: nếu mô-đun của lớp con chưa được nhập - thì lớp con đó chưa tồn tại và __subclasses__
sẽ không tìm thấy nó.
Bạn đã đề cập "cho tên của nó". Vì các lớp Python là các đối tượng hạng nhất, nên bạn không cần sử dụng một chuỗi có tên của lớp thay cho lớp hoặc bất cứ thứ gì tương tự. Bạn chỉ có thể sử dụng lớp trực tiếp, và có lẽ bạn nên.
Nếu bạn có một chuỗi đại diện cho tên của một lớp và bạn muốn tìm các lớp con của lớp đó, thì có hai bước: tìm lớp được đặt tên của nó, sau đó tìm các lớp con __subclasses__
như trên.
Cách tìm lớp từ tên tùy thuộc vào nơi bạn muốn tìm. Nếu bạn đang mong đợi tìm thấy nó trong cùng một mô-đun với mã đang cố định vị lớp, thì
cls = globals()[name]
sẽ thực hiện công việc hoặc trong trường hợp không mong muốn mà bạn mong muốn tìm thấy nó ở địa phương,
cls = locals()[name]
Nếu lớp có thể nằm trong bất kỳ mô-đun nào, thì chuỗi tên của bạn sẽ chứa tên đủ điều kiện - giống như 'pkg.module.Foo'
thay vì chỉ 'Foo'
. Sử dụng importlib
để tải mô-đun của lớp, sau đó lấy thuộc tính tương ứng:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Tuy nhiên, bạn tìm thấy lớp, cls.__subclasses__()
sau đó sẽ trả về một danh sách các lớp con của nó.
Nếu bạn chỉ muốn các lớp con trực tiếp thì .__subclasses__()
hoạt động tốt. Nếu bạn muốn tất cả các lớp con, lớp con của lớp con, v.v., bạn sẽ cần một hàm để làm điều đó cho bạn.
Đây là một hàm đơn giản, dễ đọc, tìm đệ quy tất cả các lớp con của một lớp nhất định:
def get_all_subclasses(cls):
all_subclasses = []
for subclass in cls.__subclasses__():
all_subclasses.append(subclass)
all_subclasses.extend(get_all_subclasses(subclass))
return all_subclasses
all_subclasses
là một set
để loại bỏ trùng lặp?
A(object)
, B(A)
, C(A)
, và D(B, C)
. get_all_subclasses(A) == [B, C, D, D]
.
Giải pháp đơn giản nhất ở dạng chung:
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from get_subclasses(subclass)
yield subclass
Và một lớp đối xứng trong trường hợp bạn có một lớp duy nhất mà bạn kế thừa từ:
@classmethod
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from subclass.get_subclasses()
yield subclass
__init_subclass__
Như câu trả lời khác đã đề cập, bạn có thể kiểm tra __subclasses__
thuộc tính để lấy danh sách các lớp con, vì python 3.6, bạn có thể sửa đổi việc tạo thuộc tính này bằng cách ghi đè __init_subclass__
phương thức.
class PluginBase:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
class Plugin1(PluginBase):
pass
class Plugin2(PluginBase):
pass
Bằng cách này, nếu bạn biết bạn đang làm gì, bạn có thể ghi đè hành vi của __subclasses__
và bỏ qua / thêm các lớp con từ danh sách này.
__init_subclass
lớp của cha mẹ.
Lưu ý: Tôi thấy rằng ai đó (không phải @unutbu) đã thay đổi câu trả lời được tham chiếu để nó không còn sử dụng nữa vars()['Foo']
- vì vậy điểm chính của bài đăng của tôi không còn được áp dụng.
FWIW, đây là ý tôi muốn nói về câu trả lời của @ unutbu chỉ hoạt động với các lớp được xác định cục bộ - và việc sử dụng eval()
thay vì vars()
sẽ làm cho nó hoạt động với bất kỳ lớp có thể truy cập nào, không chỉ các lớp được xác định trong phạm vi hiện tại.
Đối với những người không thích sử dụng eval()
, một cách cũng được hiển thị để tránh nó.
Đầu tiên, đây là một ví dụ cụ thể thể hiện vấn đề tiềm ẩn khi sử dụng vars()
:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
# unutbu's approach
def all_subclasses(cls):
return cls.__subclasses__() + [g for s in cls.__subclasses__()
for g in all_subclasses(s)]
print(all_subclasses(vars()['Foo'])) # Fine because Foo is in scope
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]
def func(): # won't work because Foo class is not locally defined
print(all_subclasses(vars()['Foo']))
try:
func() # not OK because Foo is not local to func()
except Exception as e:
print('calling func() raised exception: {!r}'.format(e))
# -> calling func() raised exception: KeyError('Foo',)
print(all_subclasses(eval('Foo'))) # OK
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]
# using eval('xxx') instead of vars()['xxx']
def func2():
print(all_subclasses(eval('Foo')))
func2() # Works
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]
Điều này có thể được cải thiện bằng cách chuyển eval('ClassName')
xuống hàm được xác định, giúp sử dụng dễ dàng hơn mà không mất tính tổng quát bổ sung có được bằng cách sử dụng eval()
không giống như vars()
không nhạy cảm theo ngữ cảnh:
# easier to use version
def all_subclasses2(classname):
direct_subclasses = eval(classname).__subclasses__()
return direct_subclasses + [g for s in direct_subclasses
for g in all_subclasses2(s.__name__)]
# pass 'xxx' instead of eval('xxx')
def func_ez():
print(all_subclasses2('Foo')) # simpler
func_ez()
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]
Cuối cùng, có thể, và thậm chí có thể quan trọng trong một số trường hợp, để tránh sử dụng eval()
vì lý do bảo mật, vì vậy đây là phiên bản không có:
def get_all_subclasses(cls):
""" Generator of all a class's subclasses. """
try:
for subclass in cls.__subclasses__():
yield subclass
for subclass in get_all_subclasses(subclass):
yield subclass
except TypeError:
return
def all_subclasses3(classname):
for cls in get_all_subclasses(object): # object is base of all new-style classes.
if cls.__name__.split('.')[-1] == classname:
break
else:
raise ValueError('class %s not found' % classname)
direct_subclasses = cls.__subclasses__()
return direct_subclasses + [g for s in direct_subclasses
for g in all_subclasses3(s.__name__)]
# no eval('xxx')
def func3():
print(all_subclasses3('Foo'))
func3() # Also works
# -> [<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>]
eval()
- tốt hơn bây giờ?
Một phiên bản ngắn hơn nhiều để có được danh sách tất cả các lớp con:
from itertools import chain
def subclasses(cls):
return list(
chain.from_iterable(
[list(chain.from_iterable([[x], subclasses(x)])) for x in cls.__subclasses__()]
)
)
Làm thế nào tôi có thể tìm thấy tất cả các lớp con của một lớp được đặt tên của nó?
Chúng tôi chắc chắn có thể dễ dàng thực hiện điều này khi có quyền truy cập vào chính đối tượng, vâng.
Đơn giản chỉ cần đặt tên của nó là một ý tưởng tồi, vì có thể có nhiều lớp cùng tên, thậm chí được định nghĩa trong cùng một mô-đun.
Tôi đã tạo ra một triển khai cho một câu trả lời khác , và vì nó trả lời câu hỏi này và nó thanh lịch hơn một chút so với các giải pháp khác ở đây, đây là:
def get_subclasses(cls):
"""returns all subclasses of argument, cls"""
if issubclass(cls, type):
subclasses = cls.__subclasses__(cls)
else:
subclasses = cls.__subclasses__()
for subclass in subclasses:
subclasses.extend(get_subclasses(subclass))
return subclasses
Sử dụng:
>>> import pprint
>>> list_of_classes = get_subclasses(int)
>>> pprint.pprint(list_of_classes)
[<class 'bool'>,
<enum 'IntEnum'>,
<enum 'IntFlag'>,
<class 'sre_constants._NamedIntConstant'>,
<class 'subprocess.Handle'>,
<enum '_ParameterKind'>,
<enum 'Signals'>,
<enum 'Handlers'>,
<enum 'RegexFlag'>]
Đây không phải là một câu trả lời tốt như sử dụng __subclasses__()
phương pháp lớp tích hợp đặc biệt mà @unutbu đề cập, vì vậy tôi chỉ trình bày nó như một bài tập. Các subclasses()
chức năng được xác định lợi nhuận một cuốn từ điển mà bản đồ tất cả các tên lớp con đến lớp con mình.
def traced_subclass(baseclass):
class _SubclassTracer(type):
def __new__(cls, classname, bases, classdict):
obj = type(classname, bases, classdict)
if baseclass in bases: # sanity check
attrname = '_%s__derived' % baseclass.__name__
derived = getattr(baseclass, attrname, {})
derived.update( {classname:obj} )
setattr(baseclass, attrname, derived)
return obj
return _SubclassTracer
def subclasses(baseclass):
attrname = '_%s__derived' % baseclass.__name__
return getattr(baseclass, attrname, None)
class BaseClass(object):
pass
class SubclassA(BaseClass):
__metaclass__ = traced_subclass(BaseClass)
class SubclassB(BaseClass):
__metaclass__ = traced_subclass(BaseClass)
print subclasses(BaseClass)
Đầu ra:
{'SubclassB': <class '__main__.SubclassB'>,
'SubclassA': <class '__main__.SubclassA'>}
Đây là một phiên bản không có đệ quy:
def get_subclasses_gen(cls):
def _subclasses(classes, seen):
while True:
subclasses = sum((x.__subclasses__() for x in classes), [])
yield from classes
yield from seen
found = []
if not subclasses:
return
classes = subclasses
seen = found
return _subclasses([cls], [])
Điều này khác với các triển khai khác ở chỗ nó trả về lớp gốc. Điều này là do nó làm cho mã đơn giản hơn và:
class Ham(object):
pass
assert(issubclass(Ham, Ham)) # True
Nếu get_subgroupes_gen trông hơi kỳ lạ thì đó là do nó được tạo bằng cách chuyển đổi một triển khai đệ quy đuôi thành một trình tạo vòng lặp:
def get_subclasses(cls):
def _subclasses(classes, seen):
subclasses = sum(*(frozenset(x.__subclasses__()) for x in classes))
found = classes + seen
if not subclasses:
return found
return _subclasses(subclasses, found)
return _subclasses([cls], [])