Defining __getattr__ and __getitem__ on a function has no effect - python

Disclaimer This is just an exercise in meta-programming, it has no practical
purpose.
I've assigned __getitem__ and __getattr__ methods on a function object, but
there is no effect...
def foo():
print "foo!"
foo.__getitem__ = lambda name: name
foo.__getattr__ = lambda name: name
foo.baz = 'baz'
Sanity check that we can assign properties to a function:
>>> foo.baz
'baz'
Neat. How about the "magic getters"?
>>> foo.bar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'bar'
>>> foo['foo']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'function' object is not subscriptable
>>> getattr(foo, 'bar')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'bar'
Is it possible to have a "magic getter" on a function object?

Nope! Assigning __getitem__ to an instance doesn't work on any type of object:
>>> class A(object):
... pass
...
>>> a = A()
>>> a.__getattr__ = lambda name: name
>>> a.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'foo'
And you can't define __getattr__ on the built-in function type:
>>> import types
>>> types.FunctionType.__getitem__ = lambda name: name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'function'
And you can't subclass types.FunctionType:
>>> import types
>>> class F(types.FunctionType):
... pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
type 'function' is not an acceptable base type

At least on new-style classes (which are the only kind in Python 3 and the kind you should be using in Python 2), Python only looks for magic methods on the class (and its ancestors), never on the instance. Docs here.
And of course you can't modify the function type, or derive from it. As you've found, however, any class with a __call__() method makes callable instances, so that's the way to do it.

AHHA! Use __call__, and wrap the function in F()
class F(object):
def __init__(self, fn):
self.__dict__['fn'] = fn
def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)
def __getitem__(self, name):
return name
def __getattr__(self, name):
return name
>>> foo = F(foo)
>>> f.bar
'bar'
>>> f['foo']
'foo'
>>> foo()
foo!

Related

Making copies of built-ins classes

I'm trying to write function which creates classes from classes without modifying original one.
Simple solution (based on this answer)
def class_operator(cls):
namespace = dict(vars(cls))
... # modifying namespace
return type(cls.__qualname__, cls.__bases__, namespace)
works fine except type itself:
>>> class_operator(type)
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: type __qualname__ must be a str, not getset_descriptor
Tested on Python 3.2-Python 3.6.
(I know that in current version modification of mutable attributes in namespace object will change original class, but it is not the case)
Update
Even if we remove __qualname__ parameter from namespace if there is any
def class_operator(cls):
namespace = dict(vars(cls))
namespace.pop('__qualname__', None)
return type(cls.__qualname__, cls.__bases__, namespace)
resulting object doesn't behave like original type
>>> type_copy = class_operator(type)
>>> type_copy is type
False
>>> type_copy('')
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: descriptor '__init__' for 'type' objects doesn't apply to 'type' object
>>> type_copy('empty', (), {})
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: descriptor '__init__' for 'type' objects doesn't apply to 'type' object
Why?
Can someone explain what mechanism in Python internals prevents copying type class (and many other built-in classes).
The problem here is that type has a __qualname__ in its __dict__, which is a property (i.e. a descriptor) rather than a string:
>>> type.__qualname__
'type'
>>> vars(type)['__qualname__']
<attribute '__qualname__' of 'type' objects>
And trying to assign a non-string to the __qualname__ of a class throws an exception:
>>> class C: pass
...
>>> C.__qualname__ = 'Foo' # works
>>> C.__qualname__ = 3 # doesn't work
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign string to C.__qualname__, not 'int'
This is why it's necessary to remove the __qualname__ from the __dict__.
As for the reason why your type_copy isn't callable: This is because type.__call__ rejects anything that isn't a subclass of type. This is true for both the 3-argument form:
>>> type.__call__(type, 'x', (), {})
<class '__main__.x'>
>>> type.__call__(type_copy, 'x', (), {})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__init__' for 'type' objects doesn't apply to 'type' object
As well as the single-argument form, which actually only works with type as its first argument:
>>> type.__call__(type, 3)
<class 'int'>
>>> type.__call__(type_copy, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type.__new__() takes exactly 3 arguments (1 given)
This isn't easy to circumvent. Fixing the 3-argument form is simple enough: We make the copy an empty subclass of type.
>>> type_copy = type('type_copy', (type,), {})
>>> type_copy('MyClass', (), {})
<class '__main__.MyClass'>
But the single-argument form of type is much peskier, since it only works if the first argument is type. We can implement a custom __call__ method, but that method must be written in the metaclass, which means type(type_copy) will be different from type(type).
>>> class TypeCopyMeta(type):
... def __call__(self, *args):
... if len(args) == 1:
... return type(*args)
... return super().__call__(*args)
...
>>> type_copy = TypeCopyMeta('type_copy', (type,), {})
>>> type_copy(3) # works
<class 'int'>
>>> type_copy('MyClass', (), {}) # also works
<class '__main__.MyClass'>
>>> type(type), type(type_copy) # but they're not identical
(<class 'type'>, <class '__main__.TypeCopyMeta'>)
There are two reasons why type is so difficult to copy:
It's implemented in C. You'll run into similar problems if you try to copy other builtin types like int or str.
The fact that type is an instance of itself:
>>> type(type)
<class 'type'>
This is something that's usually not possible. It blurs the line between class and instance. It's a chaotic accumulation of instance and class attributes. This is why __qualname__ is a string when accessed as type.__qualname__ but a descriptor when accessed as vars(type)['__qualname__'].
As you can see, it's not possible to make a perfect copy of type. Each implementation has different tradeoffs.
The easy solution is to make a subclass of type, which doesn't support the single-argument type(some_object) call:
import builtins
def copy_class(cls):
# if it's a builtin class, copy it by subclassing
if getattr(builtins, cls.__name__, None) is cls:
namespace = {}
bases = (cls,)
else:
namespace = dict(vars(cls))
bases = cls.__bases__
cls_copy = type(cls.__name__, bases, namespace)
cls_copy.__qualname__ = cls.__qualname__
return cls_copy
The elaborate solution is to make a custom metaclass:
import builtins
def copy_class(cls):
if cls is type:
namespace = {}
bases = (cls,)
class metaclass(type):
def __call__(self, *args):
if len(args) == 1:
return type(*args)
return super().__call__(*args)
metaclass.__name__ = type.__name__
metaclass.__qualname__ = type.__qualname__
# if it's a builtin class, copy it by subclassing
elif getattr(builtins, cls.__name__, None) is cls:
namespace = {}
bases = (cls,)
metaclass = type
else:
namespace = dict(vars(cls))
bases = cls.__bases__
metaclass = type
cls_copy = metaclass(cls.__name__, bases, namespace)
cls_copy.__qualname__ = cls.__qualname__
return cls_copy

Attributes not being inherited from parent by subclasses

I'm learning to code in python. Currently, I'm at Obstacles and Classes however I have this issue where the attributes doesn't transfer from parent, and sometimes it oddly works. What seems to be the problem?
>>> class Things:
pass
>>> class Inanimate(Things):
pass
>>> class Animate(Things):
pass
>>> class Animals(Animate):
pass
>>> class Mammals(Animals):
pass
>>> class Giraffes(Mammals):
pass
>>> class Animals(Animate):
def breathe(self):
print("breathes")
>>> class Animals(Animate):
def move(self):
print("moves")
>>> class Animals(Animate):
def eat_food(self):
print("eats food")
>>> class Animals(Animate):
def jump(self):
print("jumps in the air")
>>> class Mammals(Animals):
def feeds_young_with_milk(self):
print("feeds young with milk")
>>> class Giraffes(Mammals):
def eat_leaves_from_trees(self):
print("eat leaves from trees")
>>> reginald = Giraffes()
>>> reginald.move()
Traceback (most recent call last):
File "<pyshell#44>", line 1, in <module>
reginald.move()
AttributeError: 'Giraffes' object has no attribute 'move'
>>> reginal.breathes()
Traceback (most recent call last):
File "<pyshell#45>", line 1, in <module>
reginal.breathes()
NameError: name 'reginal' is not defined
>>> reginald.breathes()
Traceback (most recent call last):
File "<pyshell#46>", line 1, in <module>
reginald.breathes()
AttributeError: 'Giraffes' object has no attribute 'breathes'
>>> reginald.eat_food()
Traceback (most recent call last):
File "<pyshell#47>", line 1, in <module>
reginald.eat_food()
AttributeError: 'Giraffes' object has no attribute 'eat_food'
>>> reginald.jump()
jumps in the air
>>>
Unlike in, say, Ruby, redefining a class doesn't add more stuff to the existing definition. Every time you write class Animals(Animate): ..., you're defining an entirely new class with no connection to any previous class with that name, and replacing the class object that the name Animals used to refer to. The new class doesn't have the methods of the old.
Stop redefining the same classes 5 times. Completely define a class the first time around.

modify method only having the method's name and instance object

I know with getattr() you can call the method, but I need to overwrite it, so myInstance.mymethod will be overwritten.
I have the method's name as a string and the instance's reference.
You can overwrite it with setattr
>>> class Foo(object):
... def method(self): pass
...
>>> a = Foo()
>>> a.method()
>>> setattr(a,'method',1)
>>> a.method()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
To replace with another method:
>>> import types
>>> setattr(a,'method',types.MethodType(lambda self: self.__class__.__name__,a))
>>> a.method()
'Foo'
Where the lambda stuff is just fancy shorthand for defining a function:
def func(self):
return self.__class__.__name__
setattr(a,'method',types.MethodType(func,a))

Creating inmutable types in Python using __new__()

My question is pretty simple, I have:
class upperstr(str):
def __new__(cls, arg):
return str.__new__(cls, str(arg).upper())
Why, if my __new__() method is directly using an instance of an inmutable type (str), instances of my new type (upperstr) are mutable?
>>> s = str("text")
>>> "__dict__" in dir(s)
False
>>> s = upperstr("text")
>>> "__dict__" in dir(s)
True
In what stage does the interpreter sets the __dict__ attribute to upperstr intances if I'm only overriding the __new__() method?
Thanks!
All user-defined classes in Python have a __dict__() attribute by default, even if you don't overwrite anything at all:
>>> x = object()
>>> x.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute '__dict__'
>>> class MyObject(object):
... pass
...
>>> x = MyObject()
>>> x.__dict__
{}
If you don't want a new-style class to have a __dict__, use __slots__ (documentation, related SO thread):
>>> class MyObject(object):
... __slots__ = []
...
>>> x = MyObject()
>>> x.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute '__dict__'

Remove class attribute in inherited class Python

Consider such code:
class A ():
name = 7
description = 8
color = 9
class B(A):
pass
Class B now has (inherits) all attributes of class A. For some reason I want B not to inherit attribute 'color'. Is there a possibility to do this?
Yes, I know, that I can first create class B with attributes 'name' and 'description' and then inherit class A from B adding attribute 'color'. But in my exact case, B is actually a reduced version of A, so for me it seems more logical to remove attribute in B (if possible).
I think the best solution would be to change your class hierarchy so you can get the classes you want without any fancy tricks.
However, if you have a really good reason not to do this you could hide the color attribute using a Descriptor. You'll need to be using new style classes for this to work.
class A(object):
name = 7
description = 8
color = 9
class Hider(object):
def __get__(self,instance,owner):
raise AttributeError, "Hidden attribute"
def __set__(self, obj, val):
raise AttributeError, "Hidden attribute"
class B(A):
color = Hider()
You'll then get an AttributeError when you try to use the color attribute:
>>> B.color
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __get__
AttributeError: Hidden attribute
>>> instance = B()
>>> instance.color
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __get__
AttributeError: Hidden attribute
>>> instance.color = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __set__
AttributeError: Hidden attribute
You can supply a different value for color in B, but if you want B not to have some property of A then there's only one clean way to do it: create a new base class.
class Base():
name = 7
description = 8
class A(Base):
color = 9
class B(Base):
pass

Categories

Resources