vijay shanker
vijay shanker

Reputation: 2673

How can we mutate attributes on basis of attributes passed at init in metaclass

say i have a class which takes a attribute called bucket_name, on basis of this bucket i want to set an attribute bucket_path for class which is of kind bucket_path = "{bucket_name}_created".format(bucket_path='master-bucket')

I am trying to use metaclass for it which is like this:

class MyMeta(type):
    def __new__(meta, klassname, bases, attrs):
        # trying to get bucket_name here from attrs but attrs is {}
        return type.__new__(meta, klassname, bases, attrs)

class M(object):
    __metaclass__= MyMeta
    def __init__(self, bucket_name):
        self.bucket_name = bucket_name

but i am failing because attrs is empty when i do m = M('my_bucket_name'), how to go about it?

Upvotes: 0

Views: 55

Answers (1)

Olivier Melançon
Olivier Melançon

Reputation: 22324

The attribute bucket_name is an instance attribute, thus it is not possible for bucket_path to depend on bucket_name and be a class attribute at the same time.

Both must either be class attributes or instance attributes.

Instance attributes

If both are to be instance attributes, then a metaclass is unnecessary, a property will suffice.

class M(object):
    def __init__(self, bucket_name):
        self.bucket_name = bucket_name

    @property
    def bucket_path(self):
        return "{}_created".format(self.bucket_name)

m = M('foo')
print(m.bucket_path) # foo_created

Class attributes

If both are to be class attributes, then you want to define a property, but on the class. While you could define a class decorator to implement class properties, this can indeed be achieved with a metaclass as well.

class MyMeta(type):
    def __new__(cls, name, bases, namespace):
        if 'bucket_name' not in namespace:
            raise TypeError("class must have 'bucket_name'")

        # This is meant to also delegate the instance attribute to the class property
        namespace['bucket_path'] = property(lambda self: type(self).bucket_path)

        return super(MyMeta, cls).__new__(cls, name, bases, namespace)

    @property
    def bucket_path(cls):
        return "{}_created".format(cls.bucket_name)

class M(object):
    __metaclass__ = MyMeta
    bucket_name = 'foo'

print(M.bucket_path) # foo_created
print(M().bucket_path) # foo_created

Upvotes: 1

Related Questions