Hacker News new | past | comments | ask | show | jobs | submit login
Metaclasses in Python (stackoverflow.com)
158 points by fogus on Dec 15, 2011 | hide | past | favorite | 30 comments




I find metaclasses philosophically unsound. If one introduces metaclasses, what about meta-metaclasses, meta-meta-metaclasses. In this regard Self's prototypical oop seems more coherent - there are only objects. Even object/class duality looks philosophically more attractive than object/class/metaclass trio.


meta is all relative and means 'beyond'. A metaclass being a class, it can itself have a metaclass, and so on, and they are all only objects, where the author's following excerpt embodies:

    MyClass = MetaClass()
    MyObject = MyClass()
Those two code blocks are effectively equivalent:

    class Quux(type):
        #__metaclass__ = type is implicit
        pass
    
    class Qux(object):
        __metaclass__ = Quux
        pass
and:

    Quux = type('Quux', (type,), {})
    Qux = Quux('Qux', (object,), {})
the __metaclass__ attribute merely puts in words what's the callable being called when the keyword class gets parsed and subsequently handled in the current namespace.

This class/object unification is one of a bunch of unifications I find extremely satisfying when thinking about how Python ticks.


Is it me or the first metaclass example doesn't work.

I'm using Python 2.7.2+ on Debian and I can't get __metaclass__ force upper case attributes inside of a 'module'


tl;dr don't use metaclasses


Slightly longer version: don't use metaclasses unless you really know what you're doing.


Slightly shorter longer version: classes are objects, see 'type()'


If I'm not mistaken the authors of Smalltalk realized that metaclasses were a mistake long before they appeared in Python. Yet they were added to Python anyway.


Python is a very cool language. I wonder if using __metaclass__ in some fashion would allow for javascript-like prototyping.


It's interesting that you bring up Javascript. I've just started working in Python full-time recently and have noticed the OOP stuff "feels" a little like working with objects in Javascript. Specifically around how the built in super works and more generally how methods are defined on classes to begin with. In Javascript you can do interesting things with call and apply on function objects to share functionality between two objects that work the same. Similarly in Python, since the methods defined in a class are also available on the class object itself, you can use them outside of an instance and simply give them a self to operate on. The class object almost acts like the prototype object on the function object you're using as a constructor. I know the two systems are different, but they do "feel" real similar. Most people I mention this to seem a bit surprised and have that "never thought of that" look on their face. Anyone else witness this?


Jonathan Fine gave a talk on this at Europython this year.

Looks like there's a video available at http://ep2011.europython.eu/conference/talks/javascript-for-....


What prototyping stuff is missing in Python?


In javascript you can modify an object

    var foo = Object();
    foo.blah = function(x,y) { ... };
But in python, that doesn't quite work. You can only do

    foo = object()
    foo.blah = lambda x,y: ...
 
lambdas are a bit more limited as they are restricted to one line, and you can't have print statements, which makes complicated expressions rather ugly.

edit: ah, as someone noted, the second code snippet should be something like foo = Foo() where

    class Foo(object):
        pass


That's not quite right. While you're correct that lambda statements are restricted, I have never actually seen a lambda expression used to extend an object. Instead, you use a named function, which has none of these restrictions:

    foo = object()
    foo.name = "Hi thar"
    def hello(self):
       print "Hello, I'm %s" % self.name
    foo.hello = hello
This is, in fact, one of the reasons why the self parameter is explicit.


The above doesn't really work as the builtin object class doesn't allow extra attributes. Still any pure Python class does by default:

    class Foo(object):
        pass
    foo = Foo()
    foo.name = ...
    def hello(self): 
        ...
    foo.hello = hello


You should either remove `self` or bind it differently:

  foo = Foo()
  def hello():
      print("hello, %s" % (foo,))
  foo.hello = hello
`foo` is an instance therefore `self == foo` already.

  def hello(self):
      print("hello, %s" % (self,))
  foo.hello = types.MethodType(hello, foo)


Also, i forgot to add, your code snippet doesn't work. Some magic goes into the self binding.

    In [21]: def hello(self):
    ....:    print self.name
    ....: 
    In [22]: class Foo(object):
    ....:     name = 'blah'
    ....: 
    In [23]: goo = Foo()
    go
    In [24]: goo.hi = hello
    In [25]: goo.hi
    Out[25]: <function hello at 0x103985aa0>
    In [26]: goo.hi()
    TypeError                                 Traceback (most     recent call last)

    TypeError: hello() takes exactly 1 argument (0 given)


Use types.MethodType for that:

    >>> import types
    >>> 
    >>> def hello(self):
    ...     print self.name
    ... 
    >>> class Foo(object):
    ...     name = 'blah'
    ... 
    >>> goo = Foo()
    >>> goo.hi = types.MethodType(hello, Foo)
    >>> 
    >>> goo.hi()
    blah
Edit: pre-2.6 you'd use "new.instancemethod()"


Yes, or alternatively I believe

    In [27]: class Foo(object):
    ....:     pass
    ....: 
 
    In [28]: def hello(self):
    ....:     print self
    ....: 

    In [29]: blah = Foo()

    In [30]: blah.hello = hello.__get__(blah, Foo) 

    In [31]: blah.hello()
    <__main__.Foo object at 0x10397f650>


you may not have seen it, but it does work:

n [8]: class Foo(object): ...: pass ...:

In [9]: blah = Foo()

In [10]: blah.asdf = lambda x: x

In [11]: blah.asdf(3) Out[11]: 3

In [12]: blah.asdf(5) Out[12]: 5


Well in JS you could do String.prototype.fnName = function(){}; to add fnName as a method to all strings even if they are already instantiated, is that possible with Python?


No. In Python, primitives prototypes aren't mutable.

Pythonic code is meant to be modular, which leads to extreme explicitness. For example, while `string_utils.capitalize(my_string)` is a bit more longwinded than `my_string.capitalize()` in JS or even `my_string.capitalize` in Ruby, this minor bit of ongoing typing overhead will yield a beautifully explicit codebase over time.

By comparison to other dynamic languages like Javascript and Ruby, Python codebases scale effortlessly and beautifully and without the limitations of "How much freaking online documentation do I need to read to locate the imperative code that's actually running?". While that sort of necessity is the mother of beautiful documentation design you'll find in (and the ascendance of great doc writers among) the Ruby and Javascript communities, I believe its technical risks outweight those tertiary benefits. Extensive and explicit documentation is a necessity that Python does away with entirely, because the code is itself explicit. Python does this at the cost of some convenience and developer happiness. Whereas I give Ruby a 10/10 in the "day-to-day comfort" scale for us dynamic developers, and where Javascript has perhaps a 7/10, I place Python squarely at 9.

By comparison to static high-level languages like Java and ActionScript (and some dynamic languages like Perl and PHP) classical, Python is of course a breeze to write.

As a result of this explicitness, when using a Python framework, you'll rarely need consult documentation or outside advice just to find out what's happening: It's all in the code, and you just have to follow the imports and method calls down the stack.

This modularity also means Pythonists totally avoid collisions in namespacing: whereas the global namespaces of Ruby and Javascript tend to lead to headaches in large codebases, Python is built on the idea of package-as-namespace (directories) and module-as-namespace (.py files)


Great reply, thanks.



Can it be done for built-in types?


No, it can't, at least not in CPython. You can import __builtin__ and clobber str, for instance, but that will not affect the behavior of existing strings, nor will it influence the behavior of string literals.

It's something that can be done in Ruby and JavaScript, but I believe it was never allowed in Python in the name of performance (you incur overhead when every number is Fixnum with the same lookup rules as any other instance, for example), and also safety.


No, unfortunately. That's one thing both Ruby and JS have over Python.


Some Python programmers might argue that's one thing Python has over Ruby and JS.


Yup (see my response)


If you find yourself doing things like this in Python you should probably switch to Ruby.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: