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 = zdef asString (self, value):    return str(value)vector = oObject(5,5,5)# So i can doasString(vector.x)# But I want this kind of syntaxvector.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.

askedJul 8, 2012 at 18:22
MObject's user avatar
3
  • You are talking about adding custom methods to a builtinint? If thats just an example and you really mean objects, thenx would probably be a custom object with aasString methodCommentedJul 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.CommentedJul 8, 2012 at 18:29
  • You could for example do something likevector.asString('x')CommentedJul 8, 2012 at 18:32

3 Answers3

3

You could either write a custom method for youroObject class that returns the string of the givenkey, or maybe you could write a customVariant 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 theQVariant class, which is actually from Qt. Normally python wouldn't need this type, but it was necessary for C++ to represent the multiple types.

answeredJul 8, 2012 at 18:38
jdi's user avatar
Sign up to request clarification or add additional context in comments.

Comments

2

Youcannot 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 usingstr(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 bystr(x), not by calling a method namedasString. Using__str__ you can already customize whatstr 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] = fdef 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 thinkthis 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.

answeredJul 8, 2012 at 18:27
6502's user avatar

3 Comments

Yes maybe you're right i don't understand this concept. I thought that need a metaclass...
@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.
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
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 themit 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 likevector.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).

answeredJul 8, 2012 at 20:49
jsbueno's user avatar

Comments

Your Answer

Sign up orlog in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to ourterms of service and acknowledge you have read ourprivacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.