CONSTRUCT - Python's way of Ruby's "alias_method"

  • Thread starter Ilias Lazaridis
  • Start date
I

Ilias Lazaridis

I have a few small questions subjecting python functionality, most
importantly the "alias_method".

-

*IMPORT*

I would like to know, if this construct is valid, or if it can result in
problems (that I do not see as a newcomer):

1082 try:
1083 from django.rework.evolve import evolvedb
1084 except ImportError:
1085 def evolvedb():
1086 "Evolve Command Dummy"
1087 print 'Command evolvedb not imported'
1088 evolvedb.args =''

-

*PATCHING*

A second problem is, how to make the setup for users (testers) more
convenient. Is there e.g. any mechanism to apply a patch in an automated
manner (e.g. using a python library)?

-

*ALIAS_METHOD*

The django commands are hard-coded:

http://code.djangoproject.com/browser/django/trunk/django/core/management.py#L1180

thus elegant/dynamic additions of commands seem not possible.

Another possibility is to enlink (hook?) the functionality into an
existent function

Is there any way (beside a patch) to alter the behaviour to an existing
function. Is ther a python construct similar to the "alias_method" of Ruby:

(example from an simple evolution support for a ruby orm)

#------------------------------------------------------------------------------
# use "alias_method" to enlink the code
#------------------------------------------------------------------------------

class SqliteAdapter
alias_method :eek:ld_create_table, :create_table
def create_table(*args)
table_evolve(*args)
result = old_create_table(*args)
return result
end
end

http://lazaridis.com/case/persist/og-evolve.rb

-
-
-

If anyone is interested to verify the results in order to stabelize the
simple schema evolution support for django, please review the results
here:

http://case.lazaridis.com/wiki/DjangoProductEvaluation
http://case.lazaridis.com/wiki/DjangoSchemaEvolution
http://case.lazaridis.com/browser/django/rework/evolve.py
http://case.lazaridis.com/browser/django/rework/add_evolvedb_command.diff

..
 
M

Maric Michaud

Le Jeudi 08 Juin 2006 14:28, Ilias Lazaridis a écrit :
Another possibility is to enlink (hook?) the functionality into an
existent function

Is there any way (beside a patch) to alter the behaviour to an existing
function. Is ther a python construct similar to the "alias_method" of Ruby:
No, there is no special construct to do this, but we do things very similar
every day in Zope, it's called "monkey patch" :

#patch_service.py
from toto import service

def my_impl(self, *args) :
old_result = self._old_method(*args)
# ...
return new_result

if not hasattr(service, '_old_method') :
service._old_method = service.method
service.method = my_impl

once this file is imported, all future calls to "method" of service instances
will use my_impl.

--
_____________

Maric Michaud
_____________

Aristote - www.aristote.info
3 place des tapis
69004 Lyon
Tel: +33 426 880 097
 
T

Tim N. van der Leeuw

Since your question is so much about Django, you might want to ask on
Django groups.

Oops, you're not welcome there anymore, almost forgot.

But if merely reading the subject of a posting I already know who's the
poster, it's perhaps a bad sign.

Further readers of this thread might be interested in the discussions
linked from these pages:

http://en.wikipedia.org/wiki/Ilias_Lazaridis
http://www.encyclopediadramatica.com/index.php/Ilias
http://groups.google.com/group/comp...vc=1&q=ilias+EVALUATION&#doc_ae6315fda51d50a1

EOT?
 
D

Duncan Booth

Ilias said:
Is there any way (beside a patch) to alter the behaviour to an
existing function. Is ther a python construct similar to the
"alias_method" of Ruby:

This is a Python list. Would you care to explain what alias_method does?
(example from an simple evolution support for a ruby orm)

#----------------------------------------------------------------------
-------- # use "alias_method" to enlink the code
#----------------------------------------------------------------------
--------

class SqliteAdapter
alias_method :eek:ld_create_table, :create_table
def create_table(*args)
table_evolve(*args)
result = old_create_table(*args)
return result
end
end

This looks like alias_method does nothing much more than an assignment. If
you want to override a method in a base class then you can do it something
like:

class SqliteAdapter(BaseClass):
old_create_table = BaseClass.create_table
def create_table(self, *args)
self.table_evolve(*args)
result = self.old_create_table(*args)
return result

but the more usual way is just to call the original method directly in the
base class.

class SqliteAdapter(BaseClass):
def create_table(self, *args)
self.table_evolve(*args)
result = BaseClass.create_table(self, *args)
return result

If that isn't what you are trying to achieve you'll have to explain more.
 
D

Duncan Booth

Ilias said:
I would like to know, if this construct is valid, or if it can result in
problems (that I do not see as a newcomer):

1082 try:
1083 from django.rework.evolve import evolvedb
1084 except ImportError:
1085 def evolvedb():
1086 "Evolve Command Dummy"
1087 print 'Command evolvedb not imported'
1088 evolvedb.args =''

The only real problem here is that if django.rework.evolve imports
something else which doesn't exist you get your fallback code instead of
reporting the error. In other words there is a chance that you could mask a
deeper problem.

If this worries you then you could do:

try:
from django.rework.evolve import evolvedb
except ImportError, e:
if str(e).rsplit(' ')[-1] != 'django.rework.evolve':
raise
... rest of code here ...
 
M

Maric Michaud

Le Jeudi 08 Juin 2006 15:15, Duncan Booth a écrit :
but the more usual way is just to call the original method directly in the
base class.

class SqliteAdapter(BaseClass):
def create_table(self, *args)
self.table_evolve(*args)
result = BaseClass.create_table(self, *args)
return result

Yeah, this the right way to reuse ancestor's implementation of a method.
If that isn't what you are trying to achieve you'll have to explain more.
I'm not a ruby programmer, but I understood it like this : the prupose is to
modify the behavior of an existing third-party class, in all application
(even in existing third party modules), without any code modifications
(traditional patch) in those modules.

Your proposal is not as good here, assuming BaseClass is defined in module
toto, you can still do toto.BaseClass = SqliteAdapter, but you must ensure
that this code is imported before any other where classes inherit from
BaseClass. The one I porpose in my other post is robust, several packages can
even patch the same method with no side effects.


--
_____________

Maric Michaud
_____________

Aristote - www.aristote.info
3 place des tapis
69004 Lyon
Tel: +33 426 880 097
 
I

Ilias Lazaridis

Maric said:
Le Jeudi 08 Juin 2006 15:15, Duncan Booth a écrit :

Yeah, this the right way to reuse ancestor's implementation of a method.

I'm not a ruby programmer, but I understood it like this : the prupose is to
modify the behavior of an existing third-party class, in all application
(even in existing third party modules), without any code modifications
(traditional patch) in those modules.

yes, you've understood right.
Your proposal is not as good here, assuming BaseClass is defined in module
toto, you can still do toto.BaseClass = SqliteAdapter, but you must ensure
that this code is imported before any other where classes inherit from
BaseClass. The one I porpose in my other post is robust, several packages can
even patch the same method with no side effects.

Your suggestion is most possibly the relevant construct.

I'll post a note after changing the implementation.

Thank's a lot!

..
 
I

Ilias Lazaridis

Duncan said:
Ilias said:
I would like to know, if this construct is valid, or if it can result in
problems (that I do not see as a newcomer):

1082 try:
1083 from django.rework.evolve import evolvedb
1084 except ImportError:
1085 def evolvedb():
1086 "Evolve Command Dummy"
1087 print 'Command evolvedb not imported'
1088 evolvedb.args =''

The only real problem here is that if django.rework.evolve imports
something else which doesn't exist you get your fallback code instead of
reporting the error. In other words there is a chance that you could mask a
deeper problem.

If this worries you then you could do:

try:
from django.rework.evolve import evolvedb
except ImportError, e:
if str(e).rsplit(' ')[-1] != 'django.rework.evolve':
raise
... rest of code here ...

I thin I understand.

If the exception is _not_ caused by 'django.rework.evolve', then it will
be raised, thus I can see what caused it.

Very nice!

..
 
S

Slawomir Nowaczyk

On Thu, 08 Jun 2006 06:05:35 -0700

#> Since your question is so much about Django, you might want to ask
#> on Django groups.

Now *that*, and the rest of your post, was uncalled for.

This thread is perfectly on topic for this list and the question asked
concerns *Python*, with django being only a use case.

--
Best wishes,
Slawomir Nowaczyk
( (e-mail address removed) )

Some drink at the fountain of knowledge. Others just gargle.
 
S

Slawomir Nowaczyk

On Thu, 08 Jun 2006 15:28:39 +0300

#> *IMPORT*
#>
#> I would like to know, if this construct is valid, or if it can
#> result in problems (that I do not see as a newcomer):

The intricacies of import are far beyond me, but FWIW I do not see
anything wrong in this code.

#> Is there any way (beside a patch) to alter the behaviour to an
#> existing function.

You can just assign new function to the old name. There are a few
loops to hop through if you want to alter the behaviour of an existing
*method*... There is an example -- I do not know if a good one -- here:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52192

--
Best wishes,
Slawomir Nowaczyk
( (e-mail address removed) )

Strange how people who don't even know their neighbors
are extremely curious to know if there's extra-terrestrial life.
 
I

Ilias Lazaridis

Maric said:
Le Jeudi 08 Juin 2006 14:28, Ilias Lazaridis a écrit :
No, there is no special construct to do this, but we do things very similar
every day in Zope, it's called "monkey patch" :

#patch_service.py
from toto import service

def my_impl(self, *args) :
old_result = self._old_method(*args)
# ...
return new_result

if not hasattr(service, '_old_method') :
service._old_method = service.method
service.method = my_impl

once this file is imported, all future calls to "method" of service instances
will use my_impl.

Ok, just a small problem when a _function_ is to be hooked.

Looking a the code in the debugger shows that the function "syncdb" is
correctly overridden. But when the code returns, "syncdb" has again it's
original value.

Can I import "syncdb" by reference instead by value, thus the change
'survives'?

#------------------------------------------------------------------------------

#syncdb_hook.py

from django.rework.evolve import evolvedb
from django.core.management import syncdb

def syncdb_new(*args) :
evolvedb()
syncdb_result = syncdb_old(*args)
return syncdb_result

if syncdb != syncdb_new:
syncdb_old = syncdb
syncdb = syncdb_new

..
 
D

Duncan Booth

Ilias said:
Ok, just a small problem when a _function_ is to be hooked.

Looking a the code in the debugger shows that the function "syncdb" is
correctly overridden. But when the code returns, "syncdb" has again
it's original value.

Can I import "syncdb" by reference instead by value, thus the change
'survives'?

The difference is that Maric imported the module. To make the change affect
the original model you have to access the function as an attribute of its
module, not by importing the function from the module.
#----------------------------------------------------------------------
--------

#syncdb_hook.py

from django.rework.evolve import evolvedb
from django.core.management import syncdb
from django.core import management
def syncdb_new(*args) :
evolvedb()
syncdb_result = syncdb_old(*args)
return syncdb_result

if syncdb != syncdb_new:
syncdb_old = syncdb
syncdb = syncdb_new
if management.syncdb != syncdb_new:
syncdb_old = management.syncdb
management.syncdb = syncdb_new
 
I

Ilias Lazaridis

Duncan said:
The difference is that Maric imported the module. To make the change affect
the original model you have to access the function as an attribute of its
module, not by importing the function from the module.

ok, I understand.

the code below works, but has the limitation that I cannot import the
syncdb_hook within "django.core.management".

There is no way to import/get "syncdb" but mutable?
from django.core import management

if management.syncdb != syncdb_new:
syncdb_old = management.syncdb
management.syncdb = syncdb_new

works fine.

..
 
I

Ilias Lazaridis

Slawomir said:
On Thu, 08 Jun 2006 15:28:39 +0300

#> *IMPORT*
#>
#> I would like to know, if this construct is valid, or if it can
#> result in problems (that I do not see as a newcomer):

The intricacies of import are far beyond me, but FWIW I do not see
anything wrong in this code.

#> Is there any way (beside a patch) to alter the behaviour to an
#> existing function.

You can just assign new function to the old name. There are a few
loops to hop through if you want to alter the behaviour of an existing
*method*... There is an example -- I do not know if a good one -- here:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52192

This looks intresting, especially the comment, although I do not
understand what is meant by "creates a cycle in the object".

"
There is a Better Way to Add Methods To Classes, Moshe Zadka, 2001/03/15
This method creates a cycle in the object for no reason at all. The
following function will add any function to an instance in a cycle free way:

def add_method(self, method, name=None):
if name is None: name = method.func_name
class new(self.__class__): pass
setattr(new, name, method)
self.__class__ = new

Use as follows:

def pretty_str(self): pass

add_method(C(), pretty_str, '__str__')
"
 
M

Maric Michaud

Le Vendredi 09 Juin 2006 20:06, Ilias Lazaridis a écrit :
the code below works, but has the limitation that I cannot import the
syncdb_hook within "django.core.management".

In [4]: from b import CONS

In [5]: import b

In [6]: b.CONS = 3

In [7]: CONS
Out[7]: 5

In [8]: from b import CONS

In [9]: CONS
Out[9]: 3

So, if you change one module name, a function or a class or a constant, you
must do it before it is imported, or you must reload modules using it. But
either are not always possible, and the later is not what you want to achieve
here as it will re-execute all initialisation code in those modules.

But think of that, a function is hopefully an object in python, hmmm :

In [1]: from temp import func

In [2]: func(5)
Out[2]: 5

In [3]: def g(s) : return s*2
...:

In [4]: func.func_code = g.func_code

In [5]: func(5)
Out[5]: 10

hey, that should work !

--
_____________

Maric Michaud
_____________

Aristote - www.aristote.info
3 place des tapis
69004 Lyon
Tel: +33 426 880 097
 
I

Ilias Lazaridis

Maric said:
Le Vendredi 09 Juin 2006 20:06, Ilias Lazaridis a écrit :
the code below works, but has the limitation that I cannot import the
syncdb_hook within "django.core.management".

In [4]: from b import CONS

In [5]: import b

In [6]: b.CONS = 3

In [7]: CONS
Out[7]: 5

In [8]: from b import CONS

In [9]: CONS
Out[9]: 3

So, if you change one module name, a function or a class or a constant, you
must do it before it is imported, or you must reload modules using it. But
either are not always possible, and the later is not what you want to achieve
here as it will re-execute all initialisation code in those modules.

But think of that, a function is hopefully an object in python, hmmm :

In [1]: from temp import func

In [2]: func(5)
Out[2]: 5

In [3]: def g(s) : return s*2
...:

In [4]: func.func_code = g.func_code

In [5]: func(5)
Out[5]: 10

hey, that should work !

Great Construct! Much flexibility!

I'll try the implementation tomorrow.

-

The actual Versions of the hooks can be found here:

http://case.lazaridis.com/browser/django/rework/syncdb_hook.py?rev=7
http://case.lazaridis.com/browser/django/rework/startproject_hook.py?rev=13

This construct has helped to simplify nearly all simplification goals:

http://case.lazaridis.com/wiki/DjangoSchemaEvolution

..
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,011
Latest member
AjaUqq1950

Latest Threads

Top