Here's a simple function with a local function:
def raise_to(exp):
def raise_to_exp(x, exp):
return pow(x, exp)
return raise_to_exp
Now I expect the local function to close over exp, but somehow it doesn't. When I run this:
square = raise_to(2)
print(square.__closure__)
I get None. What am I missing?
There is no closure, no, because the inner function has it's own local exp variable; you gave it a parameter by that name. The parameter masks the name in the outer scope, so no closure is created for it. The function that is returned requires two arguments, and the argument to raise_to() is simply ignored:
>>> from inspect import signature
>>> def raise_to(exp):
... def raise_to_exp(x, exp):
... return pow(x, exp)
... return raise_to_exp
...
>>> signature(raise_to(2))
<Signature (x, exp)>
>>> square = raise_to(2)
>>> square(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: raise_to_exp() missing 1 required positional argument: 'exp'
>>> square(5, 3)
125
>>> raise_to('This is ignored, really')(5, 3)
125
Remove the exp parameter from the inner function if you wanted that to be taken from the outer function:
def raise_to(exp):
def raise_to_exp(x):
return pow(x, exp)
return raise_to_exp
Now exp is a closure:
>>> def raise_to(exp):
... def raise_to_exp(x):
... return pow(x, exp)
... return raise_to_exp
...
>>> raise_to(2).__closure__
(<cell at 0x11041a978: int object at 0x10d908ae0>,)
>>> raise_to.__code__.co_cellvars
('exp',)
The co_cellvars attribute on a code object gives you the names of any closed-over variable in the outer scope.
The function that is returned takes one argument, and the argument to raise_to() is now actually used:
>>> raise_to(2)(5)
25
>>> raise_to('Incorrect type for pow()')(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in raise_to_exp
TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'str'
Related
I've been trying to understand decorators. I had understood them as a function that you pass other functions through, to modify some functionality. However, the following type error is returned. Could someone explain (A) Why this is not valid? and (B) How this code should be modified such that 12 is returned?
def dec(x):
return 2*x
#dec
def func(x,y):
return x*y
>>>
TypeError Traceback (most recent call last)
<ipython-input-89-355f941cfec0> in <module>
3
4 #dec
----> 5 def func(x,y):
6 return x*y
7
<ipython-input-89-355f941cfec0> in dec(x)
1 def dec(x):
----> 2 return 2*x
3
4 #dec
5 def func(x,y):
TypeError: unsupported operand type(s) for *: 'int' and 'function'
Your decorator is given a function as an argument and needs to return a function, not a value. For example:
def dec(fn):
def newfn(x, y):
return 2 * fn(x, y)
return newfn
The point is that I am returning a function that if called will call fn internally, multiply that result by two and return that.
A decorator replaces one function with another. So this:
#dec
def mult(x, y):
return x * y
is exactly the same as this:
def mult(x, y):
return x * y
mult = dec(mult)
In both cases I am replacing mult with whatever the function dec(mult) returns. The decorator syntax is just a convenience to express that. If dec doesn't return a function then I am going to get an error when I try to call mult.
>>> def dec(fn):
... return 12
...
>>> #dec
... def mult(x, y):
... return x * y
...
>>> mult
12
>>> mult(9, 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>>
mult is 12 so that last line is "12(9, 5)" and 12 is not a callable function, hence TypeError.
I defined a function which takes 2 arguments. When I call the function, I get an error saying not enough argument:
>>> def fib(self, a,b):
... self.i=a, self.j=b
... print self.i+self.j
...
>>> fib(4,8)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: fib() takes exactly 3 arguments (2 given)
>>> fib(4,8,9)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in fib
AttributeError: 'int' object has no attribute 'i'
I passed with both 2 and 3 arguments. What should be the third argument?
I am assuming you don't understand self very well in python. Its heading towards OOP (Object oriented programming).
non-OOP approach (doing the same thing with static methods)
def fib(a,b):
print a+b
fib(4,8)
OOP approach
class Test():
i = 0
j = 0
def fib(self, a,b):
self.i=a
self.j=b
print self.i+self.j
t = Test() # create an object of Test class
t.fib(2, 3) # make the function call
NOTE : python considers a function to be a static function if it does not have the keyword self as the first parameter
You function has 3 arguments: self, a and b.
self is traditionally used for methods.
You write (simplified example):
class A:
def multiply(self, b): # method called with one argument
return 2 * b
a = A()
a.multiply(3)
or
def multiply(b): # this is a function with one argument
return 2*b
mutiply(3)
I'm aware of creating a custom __repr__ or __add__ method (and so on), to modify the behavior of operators and functions. Is there a method override for len?
For example:
class Foo:
def __repr__(self):
return "A wild Foo Class in its natural habitat."
foo = Foo()
print(foo) # A wild Foo Class in its natural habitat.
print(repr(foo)) # A wild Foo Class in its natural habitat.
Could this be done for len, with a list? Normally, it would look like this:
foo = []
print(len(foo)) # 0
foo = [1, 2, 3]
print(len(foo)) # 3
What if I want to leave search types out of the count? Like this:
class Bar(list):
pass
foo = [Bar(), 1, '']
print(len(foo)) # 3
count = 0
for item in foo:
if not isinstance(item, Bar):
count += 1
print(count) # 2
Is there a way to do this from within a list subclass?
Yes, implement the __len__ method:
def __len__(self):
return 42
Demo:
>>> class Foo(object):
... def __len__(self):
... return 42
...
>>> len(Foo())
42
From the documentation:
Called to implement the built-in function len(). Should return the length of the object, an integer >= 0. Also, an object that doesn’t define a __bool__() method and whose __len__() method returns zero is considered to be false in a Boolean context.
For your specific case:
>>> class Bar(list):
... def __len__(self):
... return sum(1 for ob in self if not isinstance(ob, Bar))
...
>>> len(Bar([1, 2, 3]))
3
>>> len(Bar([1, 2, 3, Bar()]))
3
Yes, just as you have already discovered that you can override the behaviour of a repr() function call by implementing the __repr__ magic method, you can specify the behaviour from a len() function call by implementing (surprise surprise) then __len__ magic:
>>> class Thing:
... def __len__(self):
... return 123
...
>>> len(Thing())
123
A pedant might mention that you are not modifying the behaviour of len(), you are modifying the behaviour of your class. len just does the same thing it always does, which includes checking for a __len__ attribute on the argument.
Remember: Python is a dynamically and Duck Typed language.
If it acts like something that might have a length;
class MyCollection(object):
def __len__(self):
return 1234
Example:
>>> obj = MyCollection()
>>> len(obj)
1234
if it doesn't act like it has a length; KABOOM!
class Foo(object):
def __repr___(self):
return "<Foo>"
Example:
>>> try:
... obj = Foo()
... len(obj)
... except:
... raise
...
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
TypeError: object of type 'Foo' has no len()
From Typing:
Python uses duck typing and has typed objects but untyped variable
names. Type constraints are not checked at compile time; rather,
operations on an object may fail, signifying that the given object is
not of a suitable type. Despite being dynamically typed, Python is
strongly typed, forbidding operations that are not well-defined (for
example, adding a number to a string) rather than silently attempting
to make sense of them.
Example:
>>> x = 1234
>>> s = "1234"
>>> x + s
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
You can just add a __len__ method to your class.
class Test:
def __len__(self):
return 2
a=Test()
len(a) # --> 2
I am used to having function/method definitions like so in Python:
def my_function(arg1=None , arg2='default'):
... do stuff here
If I don't supply arg1 (or arg2), then the default value of None (or 'default') is assigned.
Can I specify keyword arguments like this, but without a default value? I would expect it to raise an error if the argument was not supplied.
You can in modern Python (3, that is):
>>> def func(*, name1, name2):
... print(name1, name2)
...
>>> func()
Traceback (most recent call last):
File "<ipython-input-5-08a2da4138f6>", line 1, in <module>
func()
TypeError: func() missing 2 required keyword-only arguments: 'name1' and 'name2'
>>> func("Fred", "Bob")
Traceback (most recent call last):
File "<ipython-input-7-14386ea74437>", line 1, in <module>
func("Fred", "Bob")
TypeError: func() takes 0 positional arguments but 2 were given
>>> func(name1="Fred", name2="Bob")
Fred Bob
Any argument can be given as with a keyword expression, whether or not it has a default:
def foo(a, b):
return a - b
foo(2, 1) # Returns 1
foo(a=2, b=1) # Returns 1
foo(b=2, a=1) # Returns -1
foo() # Raises an error
If you want to force the arguments to be keyword-only, then see DSM's answer, but that didn't seem like what you were really asking.
So I have a Point class which basically consists of X and Y coordinates, and I want to create a Rectangle class that is constructed from a point at its top left corner, its width and its height.
My idea is to pass the Point object as a parameter so the Rectangle class constructor will create its own Point attribute with the argument's x and y values, which should be returned as a tuple and assigned to the new object, but it's not working.
This is my code:
class Point:
def __init__(self, init_x, init_y):
self.x = init_x
self.y = init_y
def get_point(self):
return(self.x, self.y)
class Rectangle:
def __init__(self,point,height,width):
self.top_left = Point(point.get_point())
my_point = Point(1,2)
my_rectangle = Rectangle(my_point,2,2)
print(my_rectangle.top_left)
And this is the error message:
Traceback (most recent call last):
File "/Users/mac/Desktop/programming/python/rectangle.py", line 70, in <module>
my_rectangle = Rectangle(my_point,2,2)
File "/Users/mac/Desktop/programming/python/rectangle.py", line 67, in __init__
self.top_left = Point(point.get_point())
TypeError: __init__() missing 1 required positional argument: 'init_y'
Why is it not passing the two values as a tuple? Is there a way to do what I'm trying to do?
You need to unpack the tuple using the * unpacking syntax:
self.top_left = Point(*point.get_point())
Otherwise, the tuple returned by get_point will be treated as only one argument:
>>> def func(a, b):
... return a, b
...
>>> func((1, 2))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() missing 1 required positional argument: 'b'
>>> func(*(1, 2))
(1, 2)
>>>
You are already passing a Point object here
my_rectangle = Rectangle(my_point,2,2)
so you don't have to create another Point like this
self.top_left = Point(point.get_point())
Simply do
self.top_left = point
The actual error is due to the fact that, Point class accepts two parameters in its __init__, but you are passing only one value, as a tuple.
Point(point.get_point())
Instead, you should be passing the values of x and y, by unpacking the tuple, like this
self.top_left = Point(*point.get_point())