how to overload sqrt in a module?

  • Thread starter Michael McNeil Forbes
  • Start date
M

Michael McNeil Forbes

I would like to write a module that provides some mathematical functions
on top of those defined in math, cmath etc. but I would like to make it
work with "any" type that overloads the math functions.

Thus, I would like to write:

module_f.py
----
def f(x):
""" Compute sqrt of x """
return sqrt(x)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "module_f.py", line 2, in f
return sqrt(x)
NameError: global name 'sqrt' is not defined

I understand why sqrt is not in scope, but my question is: what is the
best way to do this?

Here is one fairly ugly solution:

module_g.py
-----------
def g(x,math):
return math.sqrt(x)
1.4142135623730951j

I am sure there is a better way of doing this that makes use of the
type of the argument (Dynamical scoping would solve the
problem but we don't want to go there...). Note that the following
function would work fine

def f(x):
return abs(x)

because of the special member __abs__ attached to the type. There is no
corresponding member for sqrt, sin etc.

What is the "proper" pythonic way to do this?

Michael.
 
K

Kent Johnson

Michael said:
I would like to write a module that provides some mathematical functions
on top of those defined in math, cmath etc. but I would like to make it
work with "any" type that overloads the math functions.

Thus, I would like to write:

module_f.py
----
def f(x):
""" Compute sqrt of x """
return sqrt(x)


Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "module_f.py", line 2, in f
return sqrt(x)
NameError: global name 'sqrt' is not defined

I understand why sqrt is not in scope, but my question is: what is the
best way to do this?

You need to import math in the module that uses it. Imports in one
module don't (in general) affect the variables available in another module.

module_f.py
----
from math import sqrt # more explicit than from math import *
def f(x):
""" Compute sqrt of x """
return sqrt(x)

Kent
 
M

mforbes

I would like to write a module that provides some mathematical functions on
You need to import math in the module that uses it. Imports in one module
don't (in general) affect the variables available in another module.

module_f.py
----
from math import sqrt # more explicit than from math import *
def f(x):
""" Compute sqrt of x """
return sqrt(x)

Kent

I know, but the point is that I want to allow the user of the module to be
able to specify which sqrt function should be used depending on the type
of x. Importing math in the module would prevent the user from using f()
with complex types (or dimensioned types for another example).

That is why I suggested the ugly, but functional solution

module_g.py
-----------
def g(x,a_math):
"""Compute sqrt of x using a_math.sqrt)
return a_math.sqrt(x)

The user can at least specify to use cmath instead of math:
(2+2j)

Michael.
 
R

Robert Kern

Michael said:
Here is one fairly ugly solution:

module_g.py
-----------
def g(x,math):
return math.sqrt(x)



1.4142135623730951j

I am sure there is a better way of doing this that makes use of the
type of the argument (Dynamical scoping would solve the
problem but we don't want to go there...). Note that the following
function would work fine

def f(x):
return abs(x)

because of the special member __abs__ attached to the type. There is no
corresponding member for sqrt, sin etc.

What is the "proper" pythonic way to do this?

Use the appropriate library which already implements the features you want.

In [8]: class A(object):
...: def __init__(self, x):
...: self.x = x
...: def sqrt(self):
...: return 2*self.x
...:
...:

In [9]: a = A(10)

In [10]: import numpy

In [11]: numpy.sqrt(a)
Out[11]: 20

In [12]: numpy.sqrt(10)
Out[12]: 3.1622776601683795

In [13]: numpy.sqrt(10j)
Out[13]: (2.2360679774997898+2.2360679774997898j)

--
Robert Kern
(e-mail address removed)

"In the fields of hell where the grass grows high
Are the graves of dreams allowed to die."
-- Richard Harter
 
M

Michael McNeil Forbes

Use the appropriate library which already implements the features you want.

In [8]: class A(object):
...: def __init__(self, x):
...: self.x = x
...: def sqrt(self):
...: return 2*self.x
...:
...:

In [9]: a = A(10)

In [10]: import numpy

In [11]: numpy.sqrt(a)
Out[11]: 20

In [12]: numpy.sqrt(10)
Out[12]: 3.1622776601683795

In [13]: numpy.sqrt(10j)
Out[13]: (2.2360679774997898+2.2360679774997898j)

Thanks. This solution is essentially to make sure that all "numeric"
objects have .sqrt etc. members, and then use a library which check
against builtin types and custom types with these extensions.

That works, as long as the appropriate library exists (or I write it
myself) (and this works in my case: I did not realize that the numpy/scipy
functions worked this way.)

Are there any other more generic solutions (without having to write
"custom" functions like numpy.sqrt)?

Michael
 
D

Dennis Lee Bieber

I would like to write a module that provides some mathematical functions
on top of those defined in math, cmath etc. but I would like to make it
work with "any" type that overloads the math functions.
Since Python doesn't dispatch based on parameter types, there are
only two choices that I can see...

Either the CALLER already knows what the arguments are, and has
coded an explicit call to the version of the function desired...

OR, the caller imports and calls your special module, and your
special module has to import ALL possible override modules and supply a
function that determines which of the modules is to be used...


#custom module
import math
#import other -- since I'm just doing a two step mode where one step
# -- is strings, it is meaningless to import anything else

def sqrt(x):
try:
return math.sqrt(x)
except:
return " sqrt(%s) " % x
-=-=-=-=-=-=-
Note my simplistic version doesn't quote string arguments
--
 
R

Robert Kern

Michael said:
Are there any other more generic solutions (without having to write
"custom" functions like numpy.sqrt)?

No. *Someone* always has to write those "custom" functions. In the case of len()
and abs(), that someone was Guido. For numpy.sqrt(), that someone was Travis
Oliphant. For anything else you want, that someone is probably you.

--
Robert Kern
(e-mail address removed)

"In the fields of hell where the grass grows high
Are the graves of dreams allowed to die."
-- Richard Harter
 
M

Michael McNeil Forbes

Are there any other more generic solutions (without having to write
No. *Someone* always has to write those "custom" functions. In the case of len()
and abs(), that someone was Guido. For numpy.sqrt(), that someone was Travis
Oliphant. For anything else you want, that someone is probably you.

Thanks Guido! Thanks Travis:)

What I was thinking was: is there some way to pass a parameter to the
module on import so that the module can then use the correct environment.

If I could pass a dictionary to the module with all of the overloaded
functions, then they could become part of the module's environment and
used appropriately. I could not find a way of passing such a dictionary.

Michael.
 
M

Michael McNeil Forbes

Since Python doesn't dispatch based on parameter types, there are
only two choices that I can see...

Either the CALLER already knows what the arguments are, and has
coded an explicit call to the version of the function desired...

The caller does know in my case, but wants to reuse old code that performs
a complicated computation using the standard math functions. I was
thinking that maybe there was some way for the caller to pass the
appropriate functions to the module on import, but could not find a way of
doing this.
OR, the caller imports and calls your special module, and your
special module has to import ALL possible override modules and supply a
function that determines which of the modules is to be used...

This is essentially what numpy.sqrt() etc. does as pointed out by Robert
Kern. The numpy.sqrt() seems to check if the argument is real, complex,
or some other type and calls either math.sqrt, cmath.sqrt or x.sqrt()
appropriately. This works for my problem: I just wondered if there was a
more generic solution without having to do this work.

Thanks,
Michael.
 
D

david mugnai

On Thu, 02 Mar 2006 19:24:48 -0800, mforbes wrote:

[snip]
I know, but the point is that I want to allow the user of the module to be
able to specify which sqrt function should be used depending on the type
of x. Importing math in the module would prevent the user from using f()
with complex types (or dimensioned types for another example).

If I don't misunderstood the problem, you can define an "init" method for
your module_g

(code not tested)

module_g.py
-----------

_functions = {}
def init(mathmodule):
_function['sqrt'] = getattr(mathmodule, 'sqrt', None)

def _get(name):
try:
return _functions[name]
except KeyError:
raise TypeError("you have to initialize module_g")

def sqrt(x):
return _get('sqrt')(x)

main.py
-------

import math
import module_g

module_g.init(math)
print module_g.sqrt(2)


HTH
 
S

Steven D'Aprano

What I was thinking was: is there some way to pass a parameter to the
module on import so that the module can then use the correct environment.

If I could pass a dictionary to the module with all of the overloaded
functions, then they could become part of the module's environment and
used appropriately. I could not find a way of passing such a dictionary.

Not on import, but you can do this:


import my_module
my_module.set_environment("math") # or cmath, or numeric, or whatever

Your my_module will be like this:

# Warning: untested code.
ENVIRON = None # global variables sometimes have their uses

def f(x):
if ENVIRON is None:
raise ValueError("Uninitialised module!")
# or use a custom exception
return ENVIRON.sqrt(x)

def set_environment(name):
global ENVIRON
ENVIRON = __import__(name)


Does this help?
 
M

Michael McNeil Forbes

On Fri, 3 Mar 2006, david mugnai wrote:

[snip]
If I don't misunderstood the problem, you can define an "init" method for
your module_g

(code not tested)

module_g.py
-----------

_functions = {}
def init(mathmodule):
_function['sqrt'] = getattr(mathmodule, 'sqrt', None)

def _get(name):
try:
return _functions[name]
except KeyError:
raise TypeError("you have to initialize module_g")

def sqrt(x):
return _get('sqrt')(x)

main.py
-------

import math
import module_g

module_g.init(math)
print module_g.sqrt(2)

Thanks, this gets me close. Is there anything really bad about the
following? It works exactly as I would like, but I don't want to get in
trouble down the road:

module_f
--------
import math as my_math

def set_math(custom_math):
globals()['my_math'] = custom_math

def f(x):
return my_math.sqrt(x)
(1+1j)

Or, if one wants to use the "from __ import *" form:

from math import *

def set_math(custom_math):
globals().update(custom_math.__dict__)

def f(x):
return sqrt(x)

Michael
 
M

Michael McNeil Forbes

... you can do this:

import my_module
my_module.set_environment("math") # or cmath, or numeric, or whatever

Your my_module will be like this:

# Warning: untested code.
ENVIRON = None # global variables sometimes have their uses

def f(x):
if ENVIRON is None:
raise ValueError("Uninitialised module!")
# or use a custom exception
return ENVIRON.sqrt(x)

def set_environment(name):
global ENVIRON
ENVIRON = __import__(name)


Does this help?

Yes. However, this raises a question: Is this any different than
directly modifying the globals, or is it just syntactic sugar.

import math as my_math

def set_environment(name):
globals()['ENVIRON'] = __import__(name)

Thanks,
Michael.
 
K

Kent Johnson

Michael said:
Is there anything really bad about the
following? It works exactly as I would like, but I don't want to get in
trouble down the road:

module_f
--------
import math as my_math

def set_math(custom_math):
globals()['my_math'] = custom_math

This seems clearer to me:
def set_math(custom_math):
global my_math
my_math = custom_math
Or, if one wants to use the "from __ import *" form:

from math import *

def set_math(custom_math):
globals().update(custom_math.__dict__)

This will cause interesting trouble if custom_math doesn't implement all
the functions you use from math.

Kent
 
S

Steven D'Aprano

Yes. However, this raises a question: Is this any different than
directly modifying the globals, or is it just syntactic sugar.

The keyword "global" instructs Python to make all references to the
following name come from the global namespace. It is the correct way to
do it (but not necessarily the *only* correct way, or *always* the
correct way). In something as tiny as your example:
def set_environment(name):
globals()['ENVIRON'] = __import__(name)

it may not make any practical difference which method you use. But in
larger, more complex code, you are creating a rod for your own back. Try
running these three functions, and explain the difference in their
behaviour.

def f1():
global parrot
parrot = 3
parrot += 1
print "parrot is %d" % parrot

def f2():
globals()["parrot"] = 3
globals()["parrot"] += 1
print "parrot is %d" % parrot

def f3():
globals()["parrot"] = 3
parrot += 1
print "parrot is %d" % parrot


Directly modifying the globals is playing around with Python's internals.
You are allowed to do that, and sometimes it is the right thing to do,
otherwise Guido wouldn't have made globals() writable.

E.g. you have code where you want -- heaven knows why -- a name to refer
to both a local and a global variable. This will work:

def func():
x = 5 # x is local
globals()['x'] = 3 # but this x is global


I don't know whether writing to globals() is guaranteed to work for all
variants of Python (CPython, Jython, PyPy, IronPython ... ) or if it is
likely to change in Python 3. But even if modifying globals() is
officially allowed, it still has a whiff of the sort of code-smell that
Joel Spolsky talks about:

http://www.joelonsoftware.com/articles/Wrong.html

In my opinion, manipulating globals() is one of those things that ring
warning bells in my head. It might not be *wrong*, exactly, but I'll want
to pay extra attention to any function that does that.
 

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,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top