3

I try to make something like that :

class oObject(object):
    def __init__(self, x = 0, y = 0, z = 0):
        self.x = x
        self.y = y
        self.z = z

def asString (self, value):
    return str(value)

vector = oObject(5,5,5)

# So i can do
asString(vector.x)

# But I want this kind of syntax
vector.x.asString()

It's just an example, i don't really want to convert integrer into a string. It's more about class into a class.

3
  • You are talking about adding custom methods to a builtin int? If thats just an example and you really mean objects, then x would probably be a custom object with a asString method
    – jdi
    Commented Jul 8, 2012 at 18:25
  • Yes, i want to add dynamic custom methods. Not only for x but also for y and z. But i don't know how to do this.
    – MObject
    Commented Jul 8, 2012 at 18:29
  • You could for example do something like vector.asString('x') Commented Jul 8, 2012 at 18:32

3 Answers 3

3

You could either write a custom method for your oObject class that returns the string of the given key, or maybe you could write a custom Variant class and wrap your values:

class oObject(object):
    def __init__(self, x = 0, y = 0, z = 0):
        self.x = Variant(x)
        self.y = Variant(y)
        self.z = Variant(z)

class Variant(object):
    def __init__(self, obj):
        self._obj = obj

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.asString())

    def __str__(self):
        return self.asString()

    def asString(self):
        return str(self._obj)

    def value(self):
        return self._obj

Check out this reference as to how PyQt4 does it, with the QVariant class, which is actually from Qt. Normally python wouldn't need this type, but it was necessary for C++ to represent the multiple types.

0
2

You cannot shouldn't do this kind of things in Python.

What you can however do is implementing the standard __str__ method in the class and that is the code that will be used when converting an instance to a string using str(instance).

Technically you can play a lot of tricks in python, trying to bend the syntax to whatever you are used to, but this is a bad idea because a lot of efforts have been put on making Python more readable and you are basically destroying that work.

In Python conversion to string is done by str(x), not by calling a method named asString. Using __str__ you can already customize what str is going to return, why adding a method? If you need a way to do a custom string conversion then just define a function dispatching on the object type instead of trying to inject new methods on existing classes:

converters = dict()

def add_converter(klass, f):
    converters[klass] = f

def default_converter(x):
    return "<%s: %s>" % (x.__class__.__name__, str(x))

def mystr(x):
    return converters.get(x.__class__, default_converter)(x)

With this approach there is no "magic" (i.e. surprising) behavior and you are not wrapping things (another approach that may surprise who reads the code).

In the above example I'm not handling converter inheritance, but you can do that by using a more sophisticated lookup if you need and if you really want that (not sure it makes sense to inherit a conversion to string function, it would silently lose information).

Also if you don't understand what a metaclass is for just leave that concept alone, most probably you don't really need it. Metaclasses are a powerful but somewhat complex tool that is not needed really that often...

I think this article is a good general explanation of what metaclasses are and what you can do with them. Note that some gory details are missing and you should use official documentation to dig them.

3
  • Yes maybe you're right i don't understand this concept. I thought that need a metaclass...
    – MObject
    Commented Jul 8, 2012 at 18:45
  • 1
    @jsbueno: I think there is no reasonable way in Python to add a new method to numbers without having this construction leaking somewhere (i.e. not behaving as expected). The key point is that really is nonsense to do this kind of things in Python. Actually I think there is little sense to use this approach in general... a top-level function dispatching on argument type is cleaner and works better in any context I can think to.
    – 6502
    Commented Jul 10, 2012 at 7:44
  • It is not about adding new methods to numbers themselves- it is indeed impossible in Python - it is about adding new behavior to "numbers" or other objects that are attribute of another object. That can be done if at attribute retrieval time, the object is wrapped around suitably (as in my example bellow). The question is about the later - I did not understand from your original wording that you where talking about adding attributes to numbers themselves, as it is possible in Ruby
    – jsbueno
    Commented Jul 13, 2012 at 12:19
0

To have exactly what you are asking for is tricky in Python - that is because, when you do "instance.x.method" - Python first retrieves the attribute "x" from "instance", and them it would try to find "method" as an attribute in the "x" object itself (without any reference to the "instance" which originally had a reference to "x" that could be possibly retrieved from inside the "method" - but for frame introspection).

I said that it "could be done" - and be made to work for most types of x, but could eventually fail, or have collatteral effects, deppending on the type of the attribute "x": If you write a __setattr__ method for your class that for each attribute set on the instance, it actually creates a dynamic sub-class of that attribute - which would enable the desired methods on the new object. The draw back, is that not all types of objects can be sub-classed, and not all sub-classed objects will behave exactly like their parents. (If "x" is a function, for example). But it would work for most cases:

class Base(object):
    def __setattr__(self, name, attr):
        type_ = type(attr)
        new_dict = {}
        for meth_name in dir(self.__class__):
            function = getattr(self.__class__, meth_name)
            # Assume any methods on the class have the desired behavior and would
            # accept the attribute as it's second parameter (the first being self).

            # This could be made more robust by making a simple method-decorator
            # which would mark the methods that one wishes to be appliable
            # to attributes, instead of picking all non "_" starting methods like here:

            if not callable(function) or meth_name in new_dict or meth_name.startswith("_"):
                continue
            def pinner(f):
                def auto_meth(se, *args, **kw):
                    return f(se._container, se, *args, **kw)
                return auto_meth
            new_dict[meth_name] = pinner(function)
        # This could be improved in order to have a class-based cache of derived types
        # so that each attribute setting would only create a new_type for
        # each different type that is being set
        new_type = type(type_.__name__, (type_,), new_dict)
        try:
            attr.__class__ = new_type
        except TypeError:
            # here is the main problem withthis approach: 
            # if the type being stored can't have it's `__class__`dynamically
            # changed, we have to build a new instance of it.
            # And if the constructor can't take just the base type
            # as its building parameter, it won't work. Worse if having another instance
            # does have side-effects in the code, we are subject to those.

            attr = new_type(attr)
        attr._container = self
        super(Base, self).__setattr__(name, attr)



class oObject(Base):
    def __init__(self, x = 0, y = 0, z = 0):
        self.x = x
        self.y = y
        self.z = z

    def asString(self, attr):
        return str(attr)

And after loading these in an interactive section:

>>> v = oObject(1,2,3)
>>> v.x.asString()
'1'
>>> v.w = [1,2,3]
>>> v.w.append(3)
>>> v.w.asString()
'[1, 2, 3, 4]'
>>> 

As you can see, this can be done with normal class inheritance no need for metaclasses.

Another, more reliable approach for any Parameter type would be to use another separator for the attribute name, and the method - them you could writhe a much simpler __getattribute__ method on a base class, that would dynamically check for the request method and call it for the attribute. This approach requires no dynamic sub-classing, and is about 2 orders of magnitude simpler. The price is that you'd write something like vector.x__asString instead of the dot separator. This is actually the approach taken in the tried and tested SQLALchemy ORM for Python.

# Second approach:

class Base(object):
    separator = "__"
    def __getattr__(self, attr_name):
        if self.__class__.separator in attr_name:
            attr_name, method_name = attr_name.split(self.__class__.separator, 1)
            method = getattr(self, method_name)
            return method(getattr(self, attr_name))
        raise AttributeError

And now:

>>> class oObject(Base):
...     def __init__(self, x = 0, y = 0, z = 0):
...         self.x = x
...         self.y = y
...         self.z = z
...         
...     def asString(self, attr):
...         return str(attr)
... 
>>> 
>>> 
>>> v = oObject(1,2,3)
>>> v.x__asString
'1'

(Some more code is required if you want more parameters to be passed to the called method, but I think this is enough to get the idea).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.