Custom subclassing issue - python

Considering this sample scenario:
#!/usr/bin/python
import binascii
import cProfile
import re
class custom_str(str):
__strip_non_hex = re.compile(r'^[^:]+:|[^0-9A-Fa-f]')
def __new__(cls, value):
return super(custom_str, cls).__new__(cls, value)
#classmethod
def new(cls, value):
# creates a pure-hexadecimal string
return cls(re.sub(cls.__strip_non_hex, '', value))
class custom_type(custom_str):
def __new__(cls, value):
# here I've to use custom_str.new()
return super(custom_type, cls).__new__(cls, custom_str.new(value))
#classmethod
def new(cls, value):
return cls('hex:%s' % (binascii.hexlify(value)))
if __name__ == '__main__':
# tests
v = custom_str('666f6f')
assert v == '666f6f'
assert type(v) == custom_str
v = custom_str.new('66,6f,6f')
assert v == '666f6f'
assert type(v) == custom_str
v = custom_type('hex:66,6f,6f')
assert v == '666f6f'
assert type(v) == custom_type
v = custom_type.new('foo')
assert v == '666f6f'
assert type(v) == custom_type
# profiling
cProfile.run("custom_type.new('foo')", sort='call')
Code works, tests passes. I'm just wondering if I can avoid calling custom_str.__new__() twice.
If I change custom_type.__new__() to return custom_str.new(value) it works, but them it'll be of type custom_str instead of custom_type.
On other hand, if I change it to return super(custom_type, cls).new(value) it gets into infinite recursion.

_strip_non_hex = re.compile(r'^[^:]+:|[^0-9A-Fa-f]')
def _strip(string):
return re.sub(_strip_non_hex, '', value)
class custom_str(str):
#classmethod
def new(cls, value):
# creates a pure-hexadecimal string
return custom_str.__new__(cls, _strip(value))
class custom_type(custom_str):
def __new__(cls, value):
return super(custom_type, cls).__new__(cls, _strip(value))
#classmethod
def new(cls, value):
return cls('hex:%s' % (binascii.hexlify(value)))
Pull the non-hex-stripping logic out of new and into its own function to untangle the dependency graph.

Related

How do I properly inherit from a base class using Super() (Python 2/3)?

I have two classes, one a base class and the second needs to inherit from the base class. My issue with with the proper use of super() in this case.
When I create my class TestService, it inherits from ServiceMap, so I was thinking the desired MRO should be: TestService -> ServiceMap -> OrderedDict.
Would this mean in my initializer of TestService I should call super(ServiceMap, self).__init__(value=value, kwargs=None)? Then initialize the rest of the TestService class? (Shown below). Or do I need to re-create the initializer from ServiceMap in every inherited class? I really don't want to re-create that code since there will be multiple types of Services inheriting from type ServiceMap.
Please provide some super() guidance!
Thanks
from collections import OrderedDict
from sys import version_info
from inspect import ismethod
import json
import six
import copy
class ServiceMap(OrderedDict):
_map = None
#----------------------------------------------------------------------
def __init__(self, value=None, **kwargs):
"""Initializer object"""
self._map = OrderedDict()
if value:
if isinstance(value, dict):
for k,v in six.iteritems(value):
if isinstance(v, dict):
v = ServiceMap(value=v)
self._map[k] = v
del k,v
if kwargs:
for k,v in six.iteritems(kwargs):
self._map[k] = v
#----------------------------------------------------------------------
def items(self):
return six.iteritems(self._map)
#----------------------------------------------------------------------
def iteritems(self):
return six.iteritems(self._map)
#----------------------------------------------------------------------
def __iter__(self):
return self._map.__iter__()
#----------------------------------------------------------------------
def next(self):
return self._map.next()
#----------------------------------------------------------------------
def __setitem__(self, k, v):
self._map[k] = v
#----------------------------------------------------------------------
def __getitem__(self, k):
if k not in self._map:
# if parameter k DNE, create a empty object as ServiceMap
self[k] = ServiceMap()
return self._map[k]
#----------------------------------------------------------------------
def __setattr__(self, k, v):
if k == '_map':
super(ServiceMap, self).__setattr__(k,v)
else:
self[k] = v
#----------------------------------------------------------------------
def __getattr__(self, k):
if k == '_map':
super(ServiceMap, self).__getattr__(k)
else:
return self[k]
#----------------------------------------------------------------------
def __delattr__(self, key):
return self._map.__delitem__(key)
#----------------------------------------------------------------------
def __contains__(self, k):
return self._map.__contains__(k)
#----------------------------------------------------------------------
def __str__(self):
"""represents the object as a string"""
return json.dumps(self.as_dictionary())
#----------------------------------------------------------------------
def __repr__(self):
return str(self)
#----------------------------------------------------------------------
def as_dictionary(self):
"""
recursively iterate the object inorder to conver the ServiceMap object
to a traditional dictionary type object."""
vals = {}
for k,v in self.items():
if type(v) is ServiceMap:
vals[k] = v.as_dictionary()
else:
vals[k] = v
del k,v
return vals
#----------------------------------------------------------------------
def values(self):
return self._map.values()
#----------------------------------------------------------------------
def __cmp__(self, value):
value = ServiceMap.compare(value)
return self._map.__cmp__(value)
#----------------------------------------------------------------------
def __eq__(self, value):
value = ServiceMap.compare(value)
if not isinstance(value, dict):
return False
return self._map.__eq__(value)
#----------------------------------------------------------------------
def __ge__(self, value):
value = ServiceMap.compare(value)
return self._map.__ge__(value)
#----------------------------------------------------------------------
def __gt__(self, value):
value = ServiceMap.compare(value)
return self._map.__gt__(value)
#----------------------------------------------------------------------
def __le__(self, value):
value = ServiceMap.compare(value)
return self._map.__le__(value)
#----------------------------------------------------------------------
def __lt__(self, value):
value = ServiceMap.compare(value)
return self._map.__lt__(value)
#----------------------------------------------------------------------
def __ne__(self, value):
value = ServiceMap.compare(value)
return self._map.__ne__(value)
#----------------------------------------------------------------------
def __delitem__(self, key):
return self._map.__delitem__(key)
#----------------------------------------------------------------------
def __len__(self):
return self._map.__len__()
#----------------------------------------------------------------------
def clear(self):
self._map.clear()
#----------------------------------------------------------------------
def copy(self):
return copy.deepcopy(self)
#----------------------------------------------------------------------
def get(self, key, default=None):
return self._map.get(key, default)
#----------------------------------------------------------------------
def has_key(self, key):
return key in self._map
#----------------------------------------------------------------------
def iterkeys(self):
return self._map.iterkeys()
#----------------------------------------------------------------------
def itervalues(self):
return self._map.itervalues()
#----------------------------------------------------------------------
def keys(self):
return self._map.keys()
#----------------------------------------------------------------------
def pop(self, key, default=None):
return self._map.pop(key, default)
#----------------------------------------------------------------------
def popitem(self):
return self._map.popitem()
#----------------------------------------------------------------------
def setdefault(self, key, default=None):
self._map.setdefault(key, default)
#----------------------------------------------------------------------
def update(self, *args, **kwargs):
if len(args) != 0:
self._map.update(*args)
self._map.update(kwargs)
#----------------------------------------------------------------------
def viewitems(self):
return self._map.viewitems()
#----------------------------------------------------------------------
def viewkeys(self):
return self._map.viewkeys()
#----------------------------------------------------------------------
def viewvalues(self):
return self._map.viewvalues()
#----------------------------------------------------------------------
#classmethod
def fromkeys(cls, seq, value=None):
"""
creates a ServiceMap object from a set of keys with default values
This allows the creation of template objects.
"""
val = ServiceMap()
val._map = OrderedDict.fromkeys(seq, value)
return val
#----------------------------------------------------------------------
#classmethod
def compare(self, value):
if type(value) is ServiceMap:
return value._map
else:
return value
class TestService(ServiceMap):
_con = None
_url = None
def __init__(self, url, connection, value=None):
super(ServiceMap, self).__init__(value=value)
self._url = None
self._con = None
The simple way to inherit with Python 3 is
class TestService(ServiceMap):
def __init__(self, value=None, **kwargs):
super().__init__(value, kwargs) #equivalent to ServiceMap.__init__(self,value, kwargs)
code_specific to this class()
This gives you a "proper" ServiceMap that you then complement
You need to use:
class TestService(ServiceMap):
def __init__(self, url, connection, value=None):
super(TestService, self).__init__(value=value)
This calls the __init__() of the parent class, in your case ServiceMap and creates the desired mro.
super() is given for its first argument the point at which you would like to get the parent class. Usually, that's the same class as the one you are currently defining, so that you will get the __init__ method of the parent of the class you are currently defining. In this case, you want to use super(TestService, self). Sometimes, you'll define a subclass of TestService and you don't define the __init__ method. That means that the TestService __init__ method will be used. Since TestService explicitly used TestService for super(), you will still be getting the correct parent __init__ method. If you used self.__class__, which may be tempting, that line will go into infinite recursion for a subclass.
Use super when you are extending a method in a superclass, that is you want to run the superclass's method's code as well as that in the child class.
Your example code is not correct: you need to call
super(TestService, self)__ init __ (value=value) to run the __ init __ method of the superclass, then you can continue to initialise the child class.
If you are not extending a method, there's no need for a super call, just don't define the method in the child class and the superclass code will run.
If you are overriding a method, define the method in the subclass but don't call super.
In Python3 you don't need to pass arguments to super, you can just call
super().some_method(arg1, arg2)
(Edited to reflect Zondo's comment)

Using __setattr__ & __getattr__ with properties

I'm attempting to write a more pythonic interaction with win32com.client for my own use so I can do things like:
with Presentation(close=True) as P:
table = P[0].tables[0]
table.cells.Shape.TextFrame.TextRange.Text= 'hello'
I've managed to get the above working (very satisfying) by overloading __getattr__ and __setattr__.
I want to interact with a powerpoint table as an array not a linear object so I created the CellRange object
This is from tables.py, which handles the array views of win32com.client tables.
from itertools import product
import numpy as np
from win32com.client import pywintypes
ALIGN_LABELS = 'bottom bottom_base middle top top_base mixed'.split()
ALIGN_LABELS_N = {k: i for i, k in enumerate(ALIGN_LABELS)}
class Win32Interface(object):
def __init__(self, win32_object):
super(Win32Interface, self).__setattr__('win32_object', win32_object)
def __setattr__(self, k, v):
setattr(self.win32_object, k, v)
def __getattr__(self, v):
return getattr(self.win32_object, v)
class Cell(Win32Interface):
def __repr__(self):
return self.Shape.TextFrame.TextRange.Text
#property
def text(self):
return self.Shape.TextFrame.TextRange.Text
#text.setter
def text(self, v):
setattr(self.Shape.TextFrame.TextRange, 'Text', v)
class CellRange(object):
def __init__(self, cell_array):
super(CellRange, self).__init__()
super(CellRange, self).__setattr__('cell_array', cell_array)
def _apply_map(self, f):
func = np.vectorize(f)
return func(self.cell_array)
def __getattr__(self, k):
try:
arr = self._apply_map(lambda x: getattr(x, k))
return CellRange(arr)
except (AttributeError, pywintypes.com_error):
return getattr(self.cell_array, k)
def __setattr__(self, k, v):
if hasattr(v, 'shape'):
assert self.shape == v.shape, 'mismatched shape'
for cell, value in zip(self.cell_array.ravel(), v.ravel()):
cell.__setattr__(k, value)
else:
self._apply_map(lambda x: setattr(x, k, v))
def __repr__(self):
return self.cell_array.__repr__()
Ignoring the Table object for the moment, I want to know why
cell_range = CellRange(cell_array)
cell_range.text = 'hello'
throws up a cannot be set error. The above calls __setattr__ which then calls _apply_map to set each element of the array, this calls Cell.__setattr__. Why can I do print cell_range.text but not cell_range.text = 'hello'?
Stumbled into the solution about 10 minutes after I posted!
The answer is to use Object's __setattr__ instead of Win32Interface's
So obvious!
class Win32Interface(object):
def __init__(self, win32_object):
super(Win32Interface, self).__setattr__('win32_object', win32_object)
def __setattr__(self, k, v):
if k in self.properties:
super(Win32Interface, self).__setattr__(k, v)
else:
setattr(self.win32_object, k, v)
def __getattr__(self, v):
return getattr(self.win32_object, v)
#property
def properties(self):
class_items = self.__class__.__dict__.iteritems()
return {k:v for k, v in class_items if isinstance(v, property) and k != 'properties'}

How to define C-Enumeration types in python

I have an enumeration data type in C. How should I declare that in python-ctypes? I want this enum variable to be part of a structure and the assignment of the values to this structure would be done through memmove. After assigning, I want to display the values of each variables in the structure, and for the enum types I want to display the enum-string.
The Enumeration class suggested by Raj Kumar was broken in that it required the __init__ to be run to set a new value in a variable, and thus unusable if the value was changed on C side. Here is a fixed version thereof:
class EnumerationType(type(c_uint)):
def __new__(metacls, name, bases, dict):
if not "_members_" in dict:
_members_ = {}
for key, value in dict.items():
if not key.startswith("_"):
_members_[key] = value
dict["_members_"] = _members_
else:
_members_ = dict["_members_"]
dict["_reverse_map_"] = { v: k for k, v in _members_.items() }
cls = type(c_uint).__new__(metacls, name, bases, dict)
for key,value in cls._members_.items():
globals()[key] = value
return cls
def __repr__(self):
return "<Enumeration %s>" % self.__name__
class CEnumeration(c_uint):
__metaclass__ = EnumerationType
_members_ = {}
def __repr__(self):
value = self.value
return "<%s.%s: %d>" % (
self.__class__.__name__,
self._reverse_map_.get(value, '(unknown)'),
value
)
def __eq__(self, other):
if isinstance(other, (int, long)):
return self.value == other
return type(self) == type(other) and self.value == other.value
Now one can declare a CEnumeration:
class EBoolean(CEnumeration):
FALSE = 0
TRUE = 1
and use it:
class HeaderStruct(Structure):
_fields_ = [("param1", EBoolean),
("param2", c_uint)]
Examples:
>>> header = HeaderStruct()
>>> header.param1
<EBoolean.FALSE: 0>
>>> memmove(addressof(header), b'\x01', 1) # write LSB 0x01 in the boolean
>>> header.param1
<EBoolean.TRUE: 1>
>>> header.param1 == EBoolean.TRUE
True
>>> header.param1 == 1 # as a special case compare against ints
True
>>> header.param1.value
1L
Antti Haapala did a fantastic job answering! I, however, did run into some minor issues when using it with Python 3.2.2 that I believe are worth noting. Instead of:
class CEnumeration(c_uint):
__metaclass__ = EnumerationType
_members_ = {}
You need to do:
class CEnumeration(c_uint, metaclass = EnumerationType):
_members_ = {}
Also, int and long have been unified in Python 3 so:
def __eq__(self, other):
if isinstance(other, (int, long)):
return self.value == other
return type(self) == type(other) and self.value == other.value
Becomes:
def __eq__(self, other):
if isinstance(other, int):
return self.value == other
return type(self) == type(other) and self.value == other.value
Here is an extension of the solution from Antti Happala, using the modifications for Python 3 as suggested by Tigger, plus an exentension for arbitrary ctypes as base class (e.g. uint8 vs. uint16):
from ctypes import *
def TypedEnumerationType(tp):
class EnumerationType(type(tp)): # type: ignore
def __new__(metacls, name, bases, dict):
if not "_members_" in dict:
_members_ = {}
for key, value in dict.items():
if not key.startswith("_"):
_members_[key] = value
dict["_members_"] = _members_
else:
_members_ = dict["_members_"]
dict["_reverse_map_"] = {v: k for k, v in _members_.items()}
cls = type(tp).__new__(metacls, name, bases, dict)
for key, value in cls._members_.items():
globals()[key] = value
return cls
def __repr__(self):
return "<Enumeration %s>" % self.__name__
return EnumerationType
def TypedCEnumeration(tp):
class CEnumeration(tp, metaclass=TypedEnumerationType(tp)):
_members_ = {}
def __repr__(self):
value = self.value
return f"<{self.__class__.__name__}.{self._reverse_map_.get(value, '(unknown)')}: {value}>"
def __eq__(self, other):
if isinstance(other, int):
return self.value == other
return type(self) == type(other) and self.value == other.value
return CEnumeration
Here is a small unit test for this, showing that it actually works to differentiate between unit8 and uint16 enums:
class Foo(TypedCEnumeration(c_uint16)):
A = 42
B = 1337
class Bar(TypedCEnumeration(c_uint8)):
A = 5
B = 23
assert isinstance(Foo(Foo.A), c_uint16)
assert isinstance(Bar(Bar.A), c_uint8)
assert type(Foo.A) == int
assert Foo.A == 42
assert str(Foo(Foo.A)) == "<Foo.A: 42>"
assert str(Bar(Bar.B)) == "<Bar.B: 23>"
class FooBar(Structure):
_pack_ = 1
_fields_ = [("foo", Foo), ("bar", Bar)]
foobar = FooBar(Foo.A, Bar.B)
assert sizeof(foobar) == 3
assert foobar.foo.value == 42
assert foobar.bar.value == 23
assert [int(x) for x in bytes(foobar)] == [42, 0, 23]

More Pythonic way to define custom __eq__ method using try, assert, except

The code below works(EDIT: actually, turns out it doesn't!), but I don't like the hanging return True statement that appears after the try: except: block.
class MySlottedClass(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClass,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClass,self).__new__()
def __eq__(self, other):
for slot in self.__slots__:
try:
assert getattr(self, slot) == getattr(other,slot), "Not Equal"
except (AssertionError, AttributeError):
return False
return True
##Testing
##Note that the above class definition is just a skeleton
##The below objects are created using 4 different but identically defined classes
##In the actual problem, I am using a metaclass to make these classes dynamically
msc1 = MySlottedClassABC(a=1,b=1,c=3)
msc2 = MySlottedClassAB(a=1,b=1)
msc3 = MySlottedClassBA(b=2,a=1)
msc4 = MySlottedClassXY(x=1,y=2)
assert msc1!=msc2
assert msc2==msc3
assert msc3==msc2
assert msc2!=msc4
Is there a more pythonic way of writing the __eq__ method for this class?
The return True is fine. I think the bigger problem is using an assert for flow control. Asserts do not run at all if the user passes -O to python on the command line. You should write something more like this:
for slot in self.__slots__:
if not hasattr(other, slot) or getattr(self, slot) != getattr(other,slot):
return False
return True
Also, __slots__ needs to be defined at the class level to work, not inside __init__:
class Foo(object):
__slots__ = ['a', 'b', 'c']
If you have a variable number of items, you probably should not be using __slots__ at all.
Ugh, nevermind I figured it out. It was pretty obvious:
def __eq__(self, other):
try:
for slot in self.__slots__:
assert getattr(self, slot) == getattr(other,slot), "Not Equal"
except (AssertionError, AttributeError):
return False
else:
return True
I should probably close this question so I don't look too dumb.
Edit: Nope, no good!
Thanks to everyone's help I now understand there are lots of problems with this way of doing it. First of all, I should not be using assert for this since it is mainly for testing, and can be turned off. Second of all, the code doesn't give the expected result for MySlottedClass(a=1,b=2)==MySlottedClass(a=1,b=2,c=3).
I came up with this way instead. Note that the class definition is repeated 4 times so I can test comparison of objects of different classes below; all of the classes are identical, however, until their instances are created. Also note that in the actual use case, I am using a metaclass to generate these classes automatically (and __eq__ is defined as a part of that metaclass).
class MySlottedClassAB(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClassAB,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClassAB,self).__init__()
def __eq__(self, other):
if set(self.__slots__) != set(other.__slots__): return False
for slot in self.__slots__:
if getattr(self, slot) != getattr(other,slot):
return False
return True
def __ne__(self, other):
return not self == other
class MySlottedClassBA(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClassBA,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClassBA,self).__init__()
def __eq__(self, other):
if set(self.__slots__) != set(other.__slots__): return False
for slot in self.__slots__:
if getattr(self, slot) != getattr(other,slot):
return False
return True
def __ne__(self, other):
return not self == other
class MySlottedClassXY(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClassXY,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClassXY,self).__init__()
def __eq__(self, other):
if set(self.__slots__) != set(other.__slots__): return False
for slot in self.__slots__:
if getattr(self, slot) != getattr(other,slot):
return False
return True
def __ne__(self, other):
return not self == other
class MySlottedClassABC(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClassABC,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClassABC,self).__init__()
def __eq__(self, other):
if set(self.__slots__) != set(other.__slots__): return False
for slot in self.__slots__:
if getattr(self, slot) != getattr(other,slot):
return False
return True
def __ne__(self, other):
return not self == other
And here are the testing procedures:
##Testing
msc1 = MySlottedClassABC(a=1, b=2, c=3)
msc2 = MySlottedClassAB(a=1, b=2)
msc3 = MySlottedClassBA(b=2, a=1)
msc4 = MySlottedClassXY(x=1, y=2)
assert msc1 != msc2
assert msc2 != msc1
assert msc2 == msc3
assert msc3 == msc2
assert msc3 != msc4
assert msc4 != msc3
However, after testing Joran Beasley's answer, I discovered to my surprised it produces IDENTICAL results to that above, with much shorter and more sensible code. So it seems the best way to accomplish this is to simply compare the two __dict__ attributes.
Seems like you are trying to recreate a namedtuple. Using namedtuple will allow to create classes on the dynamically, test for equality and other interesting things. The downside is that since tuples are immutable, so to are namedtuples and you will have to create a new object instead of updating an attribute. namedtuples will not check the order of your slots, so you must order your slots lexicographically or add your own __eq__ method that accounts for slot order.
Example usage:
from collections import namedtuple
MySlottedClassAB = namedtuple("MySlottedClassAB", ['a', 'b'])
MySlottedClassABC = namedtuple("MySlottedClassABC", ['a', 'b', 'c'])
class MySlottedClassBA(namedtuple("MySlottedClassBA", ['b', 'a'])):
def addAB(self):
return self.a + self.b
msc1 = MySlottedClassAB(a=1, b=2)
msc2 = MySlottedClassBA(b=2, a=1)
msc3 = MySlottedClassABC(1, 2, 3)
print(msc1)
print(msc2)
print(msc3)
print("{} == {} is {}".format(msc1, msc1, msc1==msc1))
print("{} == {} is {}".format(msc1, msc2, msc1==msc2))
print("{} == {} is {}".format(msc1, msc3, msc1==msc3))
print("msc2.addAB() is {}".format(msc2.addAB()))
If the order of your slots and mutability are important the following will work (for python 2).
class MySlottedClassMeta(type):
def __init__(cls, name, bases, attrs):
super(MySlottedClassMeta, cls).__init__(name, bases, attrs)
def __new__(metacls, name, bases, attrs):
assert "__slots__" in attrs
attrs["_ordered_slots"] = tuple(sorted(attrs["__slots__"]))
attrs["__init__"] = create_init(attrs["__slots__"])
attrs["__eq__"] = create_eq()
attrs["__str__"] = create_str()
cls = super(MySlottedClassMeta, metacls).__new__(metacls, name, bases, attrs)
return cls
def create_init(slots):
args = ", ".join(slots)
assignments = "\n ".join("self.{0} = {0}".format(attr) for attr in slots)
init_source = """
def __init__(self, {}):
{}
""".format(args, assignments)
exec(init_source, globals(), None)
return __init__
def create_eq():
def __eq__(self, other):
try:
same_slots = self._ordered_slots == other._ordered_slots
except AttributeError:
return False
if not same_slots:
return False
return all(getattr(self, attr) == getattr(other, attr)
for attr in self._ordered_slots)
return __eq__
def create_str():
def __str__(self):
attr_values = ", ".join("{}={}".format(s, getattr(self, s)) for s in self.__slots__)
return "{}({})".format(self.__class__.__name__, attr_values)
return __str__
class MySlottedClassXY(object):
__slots__ = ['x', 'y']
__metaclass__ = MySlottedClassMeta
class MySlottedClassYX(object):
__slots__ = ['y', 'x']
__metaclass__ = MySlottedClassMeta
xy1 = MySlottedClassXY(x=1,y=2)
xy2 = MySlottedClassXY(1, 2)
yx = MySlottedClassYX(x=1, y=2)
print(xy1.__slots__)
print(yx.__slots__)
assert xy1 == xy1
assert xy1 == xy2
assert xy1 == yx
It's work noting that __slots__ are overkill in almost all circumstances. Guido Van Rossum stated that they were a premature optimisation based on unfounded fears about the performance of attribute lookups in new style classes. Guido does also state that __slots__ can reduce the memory footprint of program when you need to create lots of small objects.
I feared that all of the changes in the [new] class system were going to have a negative impact on performance. ... Thus the use of __slots__ was a way to optimize the lookup of data attributes—a fallback, if you will, in case people were disappointed with the performance impact of the new class system. This turned out unnecessary, but by that time it was of course too late to remove __slots__.
http://python-history.blogspot.co.uk/2010/06/inside-story-on-new-style-classes.html
def __eq__(self,other):
return self.__dict__== other.__dict__
should work

Inheriting methods from a metaclass

In the example enumeration code given in this question, reproduced below, why does TOKEN contain the implementations of __contains__ and __repr__ from the metaclass EnumerationType?
from ctypes import *
class EnumerationType(type(c_uint)):
def __new__(metacls, name, bases, dict):
if not "_members_" in dict:
_members_ = {}
for key,value in dict.items():
if not key.startswith("_"):
_members_[key] = value
dict["_members_"] = _members_
cls = type(c_uint).__new__(metacls, name, bases, dict)
for key,value in cls._members_.items():
globals()[key] = value
return cls
def __contains__(self, value):
return value in self._members_.values()
def __repr__(self):
return "<Enumeration %s>" % self.__name__
class Enumeration(c_uint):
__metaclass__ = EnumerationType
_members_ = {}
def __init__(self, value):
for k,v in self._members_.items():
if v == value:
self.name = k
break
else:
raise ValueError("No enumeration member with value %r" % value)
c_uint.__init__(self, value)
#classmethod
def from_param(cls, param):
if isinstance(param, Enumeration):
if param.__class__ != cls:
raise ValueError("Cannot mix enumeration members")
else:
return param
else:
return cls(param)
def __repr__(self):
return "<member %s=%d of %r>" % (self.name, self.value, self.__class__)
class TOKEN(Enumeration):
_members_ = {'T_UNDEF':0, 'T_NAME':1, 'T_NUMBER':2, 'T_STRING':3, 'T_OPERATOR':4, 'T_VARIABLE':5, 'T_FUNCTION':6}
I would expect to have exceptions thrown by the following code to the effect that __contains__ is not implemented, instead however, I receive True False.
print 2 in TOKEN, 7 in TOKEN
Both Enumeration and TOKEN are instances of EnumerationType:
>>> isinstance(Enumeration, EnumerationType)
True
>>> isinstance(TOKEN, EnumerationType)
True
And special methods for instances of new style classes are looked up in class, e.g. repr(TOKEN) is equivalent to type(TOKEN).__repr__(TOKEN), which is EnumerationType.__repr__(TOKEN).

Categories

Resources