Functions related to classes

introspection.get_subclasses(cls, include_abstract=False)

Collects all subclasses of the given class.

Parameters
  • cls – A base class

  • include_abstract – Whether abstract base classes should be included

Returns

A set of all subclasses

introspection.get_attributes(obj, include_weakref=False)

Returns a dictionary of all of obj’s attributes. This includes attributes stored in the object’s __dict__ as well as in __slots__.

Parameters
  • obj – The object whose attributes will be returned

  • include_weakref – Whether the value of the __weakref__ slot should be included in the result

Returns

A dict of {attr_name: attr_value}

introspection.safe_is_subclass(subclass, superclass)

A clone of issubclass() that returns False instead of throwing a TypeError.

New in version 1.2.

introspection.iter_slots(cls)

Iterates over all __slots__ of the given class, yielding (slot_name, slot_descriptor) tuples.

If a slot name is used more than once, all of them will be yielded in the order they appear in the class’s MRO.

Note that this function relies on the class-level __slots__ attribute - deleting or altering this attribute in any way may yield incorrect results.

Parameters

cls – The class whose slots to yield

Returns

An iterator yielding (slot_name, slot_descriptor) tuples

introspection.get_slot_names(cls)

Collects all of the given class’s __slots__, returning a set of slot names.

Parameters

cls – The class whose slots to collect

Returns

A set containing the names of all slots

introspection.get_slot_counts(cls)

Collects all of the given class’s __slots__, returning a dict of the form {slot_name: count}.

Parameters

cls – The class whose slots to collect

Returns

A collections.Counter counting the number of occurrences of each slot

introspection.get_slots(cls)

Collects all of the given class’s __slots__, returning a dict of the form {slot_name: slot_descriptor}.

If a slot name is used more than once, only the descriptor that shadows all other descriptors of the same name is returned.

Parameters

cls – The class whose slots to collect

Returns

A dict mapping slot names to descriptors

introspection.get_implicit_method_type(method_name)

Given the name of a method as input, returns what kind of method python automatically converts it to. The return value can be staticmethod, classmethod, or None.

Examples:

>>> get_implicit_method_type('frobnicate_quadrizzles')
>>> get_implicit_method_type('__new__')
<class 'staticmethod'>
>>> get_implicit_method_type('__init_subclass__')
<class 'classmethod'>

New in version 1.3.

introspection.add_method_to_class(method, cls, name=None, method_type=auto)

Adds method to cls‘s namespace under the given name.

If name is None, it defaults to method.__name__.

The method’s metadata (__name__, __qualname__, and __module__) will be updated to match the class.

If a method_type is passed in, it should have a value of staticmethod, classmethod, or None. If omitted, it is automatically determined by calling get_implicit_method_type(). The method is then automatically wrapped with the appropriate descriptor.

New in version 1.3.

Parameters
  • method – The method to add to the class

  • cls – The class to which to add the method

  • name – The name under which the method is registered in the class namespace

  • method_type – One of staticmethod, classmethod, or None (or omitted)

introspection.wrap_method(method, cls, name=None, method_type=auto)

Adds method to cls‘s namespace under the given name, wrapping the existing method (if one exists).

The replaced method will be passed in as the first positional argument (even before the implicit self). If the class previously didn’t implement this method, an appropriate dummy method will be passed in instead, which merely sends the call further up the MRO.

method_type has the same meaning as it does in add_method_to_class().

Example:

class Foo:
    def __init__(self, foo):
        self.foo = foo

    def __repr__(self):
        return f'<Foo object with foo={self.foo}>'

def custom_init(original_init, self, *args, **kwargs):
    original_init(self, *args, **kwargs)
    print('Initialized instance:', self)

wrap_method(custom_init, Foo, '__init__')

Foo(5)  # prints "Initialized instance: <Foo object with foo=5>"

Note

Adding a __new__ method to a class can lead to unexpected problems because of the way object.__new__ works.

If a class doesn’t implement a __new__ method at all, object.__new__ silently discards any arguments it receives. But if a class does implement a custom __new__ method, passing arguments into object.__new__ will throw an exception:

class ThisWorks:
    def __init__(self, some_arg):
        pass

class ThisDoesntWork:
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, some_arg):
        pass

ThisWorks(5)  # works
ThisDoesntWork(5)  # throws TypeError: object.__new__() takes exactly one argument

This is why, when this function is used to add a __new__ method to a class that previously didn’t have one, it automatically generates a dummy __new__ that attempts to figure out whether it should forward its arguments to the base class’s __new__ method or not. This is why the following code will work just fine:

class ThisWorks:
    def __init__(self, some_arg):
        pass

def __new__(original_new, cls, *args, **kwargs):
    return original_new(cls, *args, **kwargs)

wrap_method(__new__, ThisWorks)

ThisWorks(5)  # works!

However, it is impossible to always correctly figure out if the arguments should be passed on or not. If there is another __new__ method that passes on its arguments, things will go wrong:

class Parent:
    def __init__(self, some_arg):
        pass

class Child(Parent):
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)

def __new__(original_new, cls, *args, **kwargs):
    return original_new(cls, *args, **kwargs)

wrap_method(__new__, Parent)

Parent(5)  # works!
Child(5)  # throws TypeError

In such a scenario, the method sees that Child.__new__ exists, and therefore it is Child.__new__‘s responsibility to handle the arguments correctly. It should consume all the arguments, but doesn’t, so an exception is raised.

As a workaround, you can mark Child.__new__ as a method that forwards its arguments. This is done by setting its _forwards_args attribute to True:

Child.__new__._forwards_args = True

Child(5)  # works!

New in version 1.3.

Parameters
  • method – The method to add to the class

  • cls – The class to which to add the method

  • name – The name under which the method is registered in the class namespace

  • method_type – One of staticmethod, classmethod, or None (or omitted)