python: iterate using a class method
I'm still digging in python, and there are a few things that my C++ background is getting in the way. For example, I have a class
class crack(object):
def __init__(self, sz1,sz2):
self.z1 = sz1
self.z2 = sz2
def smallz_Z(self,z,z1,z2):
return z - 0.5*(z2-z1)
def get_pot(self,z):
Z = smallz_Z(z,self.z1,self.z2)
try:
result = np.sum(np.arange(self.n) * self.coeffs * (1.0 / (cmath.sqrt(Z - 1.0) * cmath.sqrt(Z + 1.0)))
* ((Z - cmath.sqrt(Z - 1.0) * cmath.sqrt(Z + 1.0)) ** np.arange(self.n))) * (2.0 / (self.z2 - self.z1))
I'm going to create a list for crack elements,
crack_list = []
crack_list.append(crack(z1,z2)) ...
And now I n开发者_StackOverfloweed to use a iterator to get all the values of potential, for example for the matplotlib contour function. I can't do things like this:
result = np.sum(crack_list.get_potential(z))
Right now I'm doing the 'traditional' way:
def potential_value(z, crack_list):
potential = complex(0, 0)
for element in crack_list:
potential = potential + element.get_potential(z)
return potential
and if I use a for loop, it says that the element crack is not iterable. Let's say that I try to
x = np.linspace(self.x1, self.x2, self.step)
y = np.linspace(self.y1, self.y2, self.step)
X, Y = np.meshgrid(x, y)
Z = X +1j*Y
F = np.vectorize(potential_value)
but here I have a problem:
F(Z, crack_list)
for element in crack_list:
TypeError: 'crack' object is not iterable
How can I do that? Thanks.
EDIT: Thanks for the answers. I'm still getting to know list comprehension, so it's still beating me. Let's say that I want to change my implementation for list comprehension, so I change the function to
def potential_value(z_list, crack_list, u_flow):
return [np.sum([c.get_potential(z) for c in crack_list]) + u_flow.get_potential_no_c(z) for z in z_list]
Where I'm trying to use now two list comprehensions at the same time, so the potential would return an array with all the results for all the z_list variables - noting that z_list should be a list for complex types. Is there a way to do that for just one complex value in z_list, without having to pass a list with just one value? Or I have to do some kind of test?
Object to be iterable needs to have __iter__()
method, which returns an iterator (or sequence). Iterator should implement method next()
, which will yield elements or raise StopIteration
is there are no more elements. As simple as that.
In addition to the get_pot()
method not returning anything, it looks like you don't define self.n
or self.coeffs
anywhere. I'm betting coeffs
is meant to be a numpy vector, and n
its length, and I'll assume they could vary between Crack
objects just like z1
and z2
.
Based on those assumptions, I tweaked your class a little:
class Crack(object): # convention is to use capitalized class names
def __init__(self, z1, z2, coeffs):
self.z1 = z1
self.z2 = z2
self.coeffs = np.array(coeffs)
self.n = len(coeffs)
def __repr__(self):
return "Crack(z1=%s, z2=%s, coeffs=%s)" % (self.z1,
self.z2,
self.coeffs)
def get_big_Z(self, little_z):
# this may not need to be its own function, unless you
# use it separately from get_potential()
return little_z - 0.5 * (self.z2 - self.z1)
def get_potential(self, z):
Z = self.get_big_Z(z)
return (np.sum(np.arange(self.n) * self.coeffs *
(1.0 / (np.sqrt(Z - 1.0) * np.sqrt(Z + 1.0))) *
((Z - np.sqrt(Z - 1.0) * np.sqrt(Z + 1.0)) **
np.arange(self.n))) *
(2.0 / (self.z2 - self.z1))
)
Now, suppose you have (or can generate) a list of the z1
, z2
, and coeffs
values for each Crack
:
>>> # dummy information, this makes three Crack objects
...
>>> z1_values = [3.0, 1.1, 0.2]
>>> z2_values = [0.01, 0.02, 0.03]
>>> coeff_values = [[1.1,1.0],
... [2.2,2.0],
... [3.3,3.0]]
The coeff
lists are converted to ndarray
when the object is instantiated.
You can make crack_list
with the built-in function map()
like this:
>>> crack_list = map(Crack, z1_values, z2_values, coeff_values)
>>> from pprint import pprint
>>> pprint(crack_list)
[Crack(z1=3.0, z2=0.01, coeffs=[ 1.1 1. ]),
Crack(z1=1.1, z2=0.02, coeffs=[ 2.2 2. ]),
Crack(z1=0.2, z2=0.03, coeffs=[ 3.3 3. ])]
Then you can compute the total potential for some z
values, say z = 5.0
or z = 3.14
like:
>>> def total_potential(z, cracks):
... return sum(c.get_potential(z) for c in cracks)
...
>>>
>>> print total_potential(5.0, crack_list)
-0.772861759407
>>> print total_potential(3.14, crack_list)
-1.99073949493
Voila. As a check, I'd also do some pen-and-paper calculations just to be sure that big equation is doing what it should be doing.
You can create a list of potentials with a list comprehension, replacing
result = np.sum(crack_list.get_potential(z))
with
result = np.sum([elem.get_potential(z) for elem in crack_list])
Map and reduce can also be used in this context.
First, I don't see that your get_pot
method returns anything. It should end with return result
i think.
Second, stop-gap measure would be to iterate over your list of objects:
[e.get_pot(i) for i in range(10)]
But ideally, since you are using numpy, you should get your class to accept arrays (which it looks like it might already). See what happens when you input sz1
, sz2
, and z
as equal-shape 2D arrays. The, if you sum over just one axis, get_pot
should then return an iterable array. This would avoid creating lists of instances and take advantage of speedups that numpy offers. (The effectiveness of this solution is a bit dependent on the number of crack instances you have and how often you create and destroy new instances)
精彩评论