[Tutor] How to know the internal execution flow of class

Mats Wichmann mats at wichmann.us
Sat Feb 15 12:45:41 EST 2020


On 2/15/20 8:08 AM, Deepak Dixit wrote:
> Hi Tutors,
> 
> What happens when we create a class or function. In other way, I want to
> know the life-cycle of class. Lets see an example:
> 
> ######### Script Start #########
> 
> class MClass(type):
>     def __new__(cls, *args):
>         print 'New from MClass', args[0]
>         return super(MClass, cls).__new__(cls, *args)
> 
> class A:
>     print 'Class A defined'
>     __metaclass__ = MClass
> 
>     def __init__(self, *args):
>         print 'Init from A', args
> 
>     def __new__(cls, *args):
>         print 'New from A', args
>         return super(A, cls).__new__(cls, *args)
> 
> print 'Class A definition end'
> a = A()
> print 'Object created: A'
> 
> class B:
>     print 'Class B defined'
>     __metaclass__ = MClass
> 
>     def __init__(self, *args):
>         print 'Init from B', args
> 
>     def __new__(cls, *args):
>         print 'New from B', args
> 
> print 'Class B definition end'
> b = B()
> print 'Object created: B'
> 
> ######### Script End #########
> 
> #################  Output  ############
> Class A defined
> New from MClass A
> Class A definition end
> New from A ()
> Init from A ()
> Object created: A
> Class B defined
> New from MClass B
> Class B definition end
> New from B ()
> Object created: B
> 
> #################  Output  End ############
> From this example , we can see that when we are defining any class ( not
> creating object), it call some methods from the metaclass. If I am
> returning something from __new__ then it call __init__ of current class
> otherwise it skips the calling even when I am creating object. Now suppose
> that If I never assigned any metaclass manually then I will not be aware
> that something is happening when I am defining the class.
> 
> How can I know these internals?

First off here's a moderately famous quote from one of the most famous
Python developers:

    “Metaclasses are deeper magic than 99% of users should ever worry
about. If you wonder whether you need them, you don’t (the people who
actually need them know with certainty that they need them, and don’t
need an explanation about why).”

    — Tim Peters

Ignoring any niggles about Python 2 and old-style classes, if you don't
define a metaclass, your class has one anyway: it's "type". So this same
flow happens for every class statement. Every callable object has a
special method __call__ which is invoked when you "call" that object, a
metaclass is no different.  A metaclass is a template for creating a
class similar to how a class is a template for creating an object - you
call it and you get the object back.  Thus when you create class A or B,
the metaclass is called - by invoking __call__, which invokes the
__new__ and __init__ methods, and you see your message from the
metaclass's init. It you don't provide a metaclass (which inherits from
type), or don't provide those methods, then the ones from type are
called as with any inheritance relationship.  Since you can't muck with
type for safety reasons, inserting a custom metaclass is useful for
experimentation, as you've done.  For the rest, see Tim's quote :)

Seriously, did that clear any of the confusion up?

Btw. the more preferred form now is:

class A(metaclass=MClass):
    print 'Class A defined'

as opposed to defining __metaclass__


P.S. - stuff you didn't ask for:

You can create a class by calling type directly, by the way; the magic
of the class statement is you don't have to do that, since it
automatically calls type (or any metaclass you supply).  Here's
something  I had sitting around from a tutorial I did for someone. Maybe
it will amplify a little more? Here are the pieces of what we think of
as a Python class:

# some methods
def transact(acct, amount):
    acct.balance += amount

def pay_interest(acct):
    acct.balance += acct.balance * acct.interest_rate

def account_init(acct, num, name, bal, rate):
    acct.acct_number = num
    acct.acct_holder = name
    acct.balance = bal
    acct.interest_rate = rate

# a mapping for methods and attributes:
account = {
    "acct_number": "",
    "acct_holder": "",
    "balance": 0.0,
    "interest_rate": 0.0,
    "transact": transact,
    "pay_interest": pay_interest,
    "__init__": account_init,
}

# and create it with name, parents (none), mapping:
AccountType = type("AccountType", (), account)


# that gives the same result as this:
class AccountType():
    def transact(self, amount):
        self.balance += amount

    def pay_interest(self):
        self.balance += self.balance * self.interest_rate

    def __init__(self, num, name, bal, rate):
        self.acct_number = num
        self.acct_holder = name
        self.balance = bal
        self.interest_rate = rate

# you can call either AccountType to create an instance - they're the
same thing!


More information about the Tutor mailing list