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.