-2

I understand that I created a np.poly1d object. But what does it mean by putting it back inside np.poly1d() again?

import numpy as np
f = np.poly1d([1, 1, 1])
print(np.poly1d(f))

FYI running this script, I got

   2
1 x + 1 x + 1
luk2302
  • 55,258
  • 23
  • 97
  • 137
h218614
  • 9
  • 1
  • 1
    It's ordinary function-call syntax. What `np.poly1d` *does* with a value of `np.poly1d` is not a question of syntax. – chepner Mar 02 '22 at 15:07
  • (mkrieger1 more correctly notes it as *call* syntax in [his answer](https://stackoverflow.com/a/71324952/1126841), as it doesn't necessarily involve an object of type `function`.) – chepner Mar 02 '22 at 15:19

3 Answers3

2

It's call syntax.

Reference: https://docs.python.org/3/reference/expressions.html#calls

np.poly1d being the callable and f the argument.

np.poly1d is a class, which can be used like a callable to create an instance of the class (What is a "callable"?).


In this particular case, f will be interpreted as an array-like of polynomial coefficients, resulting in a new polynomial which is equivalent to f, since treating a np.poly1d instance as an array results in an array of its coefficients.

>>> np.array(f)
array([1, 1, 1])
>>> g = np.poly1d(f)
>>> g == f
True

So without knowing more context, using np.poly1d(f) instead of f seems pointless. It could be useful if the intention was to create a copy of a polynomial in order to modify one but not the other, since f and g are different objects:

>>> g is f
False
mkrieger1
  • 19,194
  • 5
  • 54
  • 65
  • Not OP, but thanks for the link. As someone that has been learning python, but has dealt with OO and other patterns in many other languages, I'm finding it hard to wrap my head around what "callable" means and its utility. Furiously reading this trying to make sense of it. – JNevill Mar 02 '22 at 15:11
  • 1
    "Callable" is just a term used to avoid using the word "function" in different contexts. A value of type `function` is what a `def` statement or lambda expression produces, and is just one kind of callable value. Other examples are types themselves (you call them in order to get an instance of the type). In Python instances of *any* type that defines a `__call__` method is callable, and intuitively can be thought of as a function, even if it is not a `function`. – chepner Mar 02 '22 at 15:15
  • In some sense, the calling syntax is syntactic sugar for a method call; `foo()` is "short" for `type(foo).__call__(foo)`. (Don't take that idea too far, otherwise you'll create an infinite list of calls, as `type(foo).__call__` itself has a `__call__` method, which has *its* own `__call__` method, etc.) – chepner Mar 02 '22 at 15:17
  • @chepner Is this same, conceptually, as an 'iteratable'. Where you can have objects that are collection-like, but not iteratable, and others that are? I'm thinking from an OO standpoint a class generally has a constructor method, and in python it's `__init__()`. In the same sense it can have a `__call__` and an `__iter__`? If I'm thinking about this correctly I can understand why Python is so popular. – JNevill Mar 02 '22 at 15:48
  • But `id(f.coeffs)` is the same as `id(g.coeffs)`. `g` is a new `poly1d` instance, but it has the same `coeffs` attribute array. For some obscure reason (carelessness?), `poly1d` docs uses the `>>> print(np.poly1d(p))` example. – hpaulj Mar 02 '22 at 18:35
1

np.poly1d appears to be an old, and somewhat non-standard class definition. There's a note at the start

This forms part of the old polynomial API.

It is also compiled, so there's no Python class definition to read. It also does not start with a capital letter, as is normal Python class practice.

You appear to be working from the first example:

>>> p = np.poly1d([1, 2, 3])
>>> print(np.poly1d(p))
   2
1 x + 2 x + 3

Why they use that print instead of print(p) is a mystery. May be it's just some sloppiness that no one bothered to fix.

np.poly1d([1,2,3]) makes a poly1d class object.

In [63]: f = np.poly1d([1, 1, 1])
In [64]: type(f)
Out[64]: numpy.poly1d
In [65]: f
Out[65]: poly1d([1, 1, 1])
In [66]: print(f)
   2
1 x + 1 x + 1

Out[65] is the repr display of this object; Out[66] is the str display.

In [67]: f1 = np.poly1d(f)
In [68]: type(f1)
Out[68]: numpy.poly1d
In [69]: id(f)
Out[69]: 139789855504368
In [70]: id(f1)
Out[70]: 139789855360240

Passing a np.poly1d object to the class creator appears to make a new poly1d object, but with the same attributes. I don't see that documented.

So in terms of Python syntax, both lines are function calls. The details of what happens, or not, are internal to the np.poly1d.

edit

Normal class definition:

In [75]: class Foo:
    ...:     def __init__(self, x):
    ...:         self.x = x
    ...: 
    ...:     def __repr__(self):
    ...:         return "A Foo <%s>" % self.x
In [76]: g = Foo("xxx")
In [77]: g
Out[77]: A Foo <xxx>
In [81]: type(g)
Out[81]: __main__.Foo

[76] uses the class name to actually call Foo.__init__ method, returning a Foo instance. print(g) or in this (ipython) case g displays the instance's repr.

With reference to your comments in another answer, I did not define a __call__ method for the Foo class, so its instances are not callable.

In [94]: g()
Traceback (most recent call last):
  Input In [94] in <module>
    g()
TypeError: 'Foo' object is not callable

Nor is it iterable

In [96]: for x in g:
    ...:     print(x)
Traceback (most recent call last):
  Input In [96] in <module>
    for x in g:
TypeError: 'Foo' object is not iterable

In contrast f, the np.poly1d instance is both callable and iterable:

In [97]: f()
Traceback (most recent call last):
  Input In [97] in <module>
    f()
TypeError: __call__() missing 1 required positional argument: 'val'

In [98]: f(3)
Out[98]: 13
In [99]: for x in f:
    ...:     print(x)
1
1
1
In [100]: f.coeffs
Out[100]: array([1, 1, 1])

That functionality was defined in the compiled code for that class.

dbl edit

np.poly1d docs says the first arg is:

c_or_r : array_like

Usually that mean the argument is first passed through np.asarray.

In [147]: np.asarray(f)
Out[147]: array([1, 1, 1])
In [148]: id(_)
Out[148]: 139789855355984
In [149]: id(f.coeffs)
Out[149]: 139789855355984
In [150]: id(f1.coeffs)
Out[150]: 139789855355984

The f1 instance created in [67] has the coeffs array as f. np.poly1d(f) works it is effectively

np.poly1d(np.asarray(f))  
np.poly1d(f.coeffs)
hpaulj
  • 221,503
  • 14
  • 230
  • 353
0

You have np.poly1d, which is a class.

Doing this:

f = np.poly1d([1, 1, 1])

you are initializing an instance of that class, in other words you are calling its __new__ and its __init__ methods.


When you use () after something, is because that something is a callable, that may be defined as @chepner did in the comments:

In Python any type that defines a __call__ method is callable

For example, a function is callable, a method is callable, and also something like this:

class MyClass:
    def __call__(self):
        ...

is callable.

In Python you can check if something is callable with the built-in function callable.

FLAK-ZOSO
  • 3,873
  • 4
  • 8
  • 28