Other Core Object Changes¶
This page details miscellaneous changes to core objects: functions and classes.
Function Attributes¶
- Fixer:
python-modernize -wnf fissix.fixes.fix_funcattrs(but see below) - Prevalence: Rare
In Python, functions are mutable objects that support custom attributes. In such cases, special attributes (ones provided or used by the Python language itself) are prefixed and postfixed by double underscores.
Function objects predate this convention: their built-in attributes
were named with the func_ prefix instead.
However, the new “dunder” names were available, as aliases, even in Python 2.
Python 3 removes the old names for these attributes:
| Legacy name | New name |
|---|---|
func_closure |
__closure__ |
func_code |
__code__ |
func_defaults |
__defaults__ |
func_dict |
__dict__ |
func_doc |
__doc__ |
func_globals |
__globals__ |
func_name |
__name__ |
The recommended fixer will replace all of the old attribute names with the
new ones.
However, it does not check that the attribute is retreived from
a function object.
If your code uses the func_* names for other purposes, you’ll need to
revert the fixer’s changes.
__oct__, __hex__¶
- Fixer: None
- Prevalence: Rare
The __oct__ and __hex__ special methods customized conversion of
custom classes to octal or hexadecimal srting representation, i.e. the behavior
of the oct() and hex() built-in functions.
Python 3 adds the bin() function, which converts to binary.
Instead of introducing a third name like __bin__, all three now just
use the integer representation of an object, as returned by the __index__
method.
The __oct__ and __hex__ methods are no longer used.
To support both Python 2 and 3, all three must be specified:
def IntLike:
def __init__(self, number):
self._number = int(number)
def __index__(self):
return self._number
def __hex__(self):
return hex(self._number)
def __oct__(self):
return oct(self._number)
If your code defines __oct__ or __hex__, add an __index__ method
that returns an appropriate integer.
If your __oct__ or __hex__ did not return an octal/hexadecimal
representation of an integer before, you’ll need to change any code that
relied on them.
Old-style slicing: __getslice__, __setslice__, __delslice__¶
- Fixer: None
- Prevalence: Rare
The special methods __getslice__, __setslice__ and __delslice__,
which had been deprecated since Python 2.0, are no longer used in Python 3.
Item access was unified under __getitem__, __setitem__ and
__delitem__.
If your code uses them, convert them into equivalent __getitem__,
__setitem__ and __delitem__, possibly adding the functionality
to existing methods.
Keep in mind that slice objects have a step attribute
in addition to start and stop.
If your class does not support all steps, remember to raise an error for
the ones you don’t support.
For example, the equivalent of:
class Slicable(object):
def __init__(self):
self.contents = list(range(10))
def __getslice__(self, start, stop):
return self.contents[start:stop]
def __setslice__(self, start, stop, value):
self.contents[start:stop] = value
def __delslice__(self, start, stop):
del self.contents[start:stop]
would be:
class Slicable(object):
def __init__(self):
self.contents = list(range(10))
def __getitem__(self, item):
if isinstance(item, slice):
if item.step not in (1, None):
raise ValueError('only step=1 supported')
return self.contents[item.start:item.stop]
else:
raise TypeError('non-slice indexing not supported')
def __setitem__(self, item, value):
if isinstance(item, slice):
if item.step not in (1, None):
raise ValueError('only step=1 supported')
self.contents[item.start:item.stop] = value
else:
raise TypeError('non-slice indexing not supported')
def __delitem__(self, item):
if isinstance(item, slice):
if item.step not in (1, None):
raise ValueError('only step=1 supported')
del self.contents[item.start:item.stop]
else:
raise TypeError('non-slice indexing not supported')
Customizing truthiness: __bool__¶
- Fixer: None
- Prevalence: Common
Python 2 used the __nonzero__ method to convert an object to boolean,
i.e. to provide an implementation for bool().
Other special methods that implement behavior for built-in functions
are named after their respective functions.
Keeping with this theme, Python 3 uses the name __bool__ instead of
__nonzero__.
To make your code compatible, you can provide one implementation, and use an alias for the other name:
class Falsy(object):
def __bool__(self):
return False
__nonzero__ = __bool__
Do this change in all classes that implement __nonzero__.
Unbound Methods¶
Python 2 had two kinds of methods: unbound methods, which you could retreive from a class object, and bound methods, which were retreived from an instance:
>>> class Hello(object):
... def say(self):
... print('hello world')
...
>>> hello_instance = Hello()
>>> print(Hello.say)
<unbound method Hello.say>
>>> print(hello_instance.say)
<bound method Hello.say of <__main__.Hello object at 0x7f6f40afa790>>
Bound methods inject self in each call to the method:
>>> hello_instance.say()
hello world
Unbound methods checked if their first argument is an instance of the appropriate class:
>>> Hello.say(hello_instance)
hello world
>>> Hello.say(1)
TypeError: unbound method say() must be called with Hello instance as first argument (got int instance instead)
In Python 3, the concept of unbound methods is gone. Instead, regular functions are used:
>>> class Hello(object):
... def say(self):
... print('hello world')
...
>>> print(Hello.say)
<function Hello.say at 0x7fdc2803cd90>
If your code relies on unbound methods type-checking the self argument,
or on the fact that unbound methods had a different type than functions,
you will need to modify your code.
Unfortunately, there is no automated way to tell if that’s the case.