question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Does not find examples when @property also has a @....setter defined

See original GitHub issue

Running Python 3.8.3 (set up with pyenv), xdoctest 0.12.0 (installed via pip install xdoctest[all]). In a class, when I have an @property defined, with a nice docstring, and then also define a setter, xdoctest does not see the example. Here’s a minimum example:

class Test(object):
    @property
    def test(self):
        """
        Example:
            >>> ini = Test()
            >>> ini.test
            3.14
        """
        return 3.14

    @test.setter
    def test(self, s):
        pass

The output I receive when I run xdoctest test_min.py is:

=====================================
_  _ ___  ____ ____ ___ ____ ____ ___
 \/  |  \ |  | |     |  |___ [__   |
_/\_ |__/ |__| |___  |  |___ ___]  |

=====================================

Start doctest_module('xdoctest_min.py')
Listing tests
gathering tests
running 0 test(s)
============
===  in 0.00 seconds ===

If I remove the whole @test.setter method, the test succeeds properly. Any ideas on what might be going wrong here / what I’m missing?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
Erotemiccommented, Jul 10, 2020

I downgraded pytest-cov in the CI scripts so the release scripts would pass. Xdoctest version 0.13.0 contains this fix and is now on pypi.

1reaction
Erotemiccommented, Jul 10, 2020

The google example is consistent. Its ok to document getters, but you don’t have to. Its also consistent in disallowing (by convention) documentation in setters / deleters. This is likely because those __doc__ attributes are not top-level accessible in the Python runtime state. (e.g when you do self.__class__.myprop.__doc__ you get the getter’s doc by default, although it is possible to do self.__class__.myprop.fset.__doc__)

I did some digging of my own and came up with this example to disect the exact behavior of properties:

# This demonstrates dynamics of properties with clobbering any
# namespace variables, so its entirely clear what variables exist and how they
# are transformed as the property dectorators are applied
import pytest


def my_getter_func(self):
    " getter doc"
    print('call getter')
    return 'value'


def my_setter_func(self, value):
    " setter doc"
    print('call setter for value = {!r}'.format(value))


def my_deleter_func(self):
    " deleter doc"
    print('call deleter')


# Use the property decorator directly with normal call syntax
# Note properties --- like most other decorators --- return new function
# objects and do not change the underlying function object. Hense we can still
# print the original functions and see how they are assigned to the fset / fget
# / fdel attributes of the returned property object.
my_getter_prop = property(my_getter_func)
my_setter_prop = my_getter_prop.setter(my_setter_func)
my_deleter_prop = my_setter_prop.deleter(my_deleter_func)

print('my_getter_func = {!r}'.format(my_getter_func))
print('my_setter_func = {!r}'.format(my_setter_func))
print('my_deleter_func = {!r}'.format(my_deleter_func))

print('my_getter_prop = {!r}'.format(my_getter_prop))
print('my_setter_prop = {!r}'.format(my_setter_prop))
print('my_deleter_prop = {!r}'.format(my_deleter_prop))

print('my_getter_prop = {!r}'.format(my_getter_prop))
print('my_getter_prop.fget = {!r}'.format(my_getter_prop.fget))
print('my_getter_prop.fset = {!r}'.format(my_getter_prop.fset))
print('my_getter_prop.fdel = {!r}'.format(my_getter_prop.fdel))
print('my_getter_prop.__doc__ = {!r}'.format(my_getter_prop.__doc__))

print('my_setter_prop = {!r}'.format(my_setter_prop))
print('my_setter_prop.fget = {!r}'.format(my_setter_prop.fget))
print('my_setter_prop.fset = {!r}'.format(my_setter_prop.fset))
print('my_setter_prop.fdel = {!r}'.format(my_setter_prop.fdel))
print('my_setter_prop.__doc__ = {!r}'.format(my_setter_prop.__doc__))

print('my_deleter_prop = {!r}'.format(my_deleter_prop))
print('my_deleter_prop.fget = {!r}'.format(my_deleter_prop.fget))
print('my_deleter_prop.fset = {!r}'.format(my_deleter_prop.fset))
print('my_deleter_prop.fdel = {!r}'.format(my_deleter_prop.fdel))
print('my_deleter_prop.__doc__ = {!r}'.format(my_deleter_prop.__doc__))

# Note: each function has its own doc, but only the doc of the getter is stored

# my_getter_func          = <function my_getter_func at 0x7f2f14a6d700>
# my_setter_func          = <function my_setter_func at 0x7f2f14a6d0d0>
# my_deleter_func         = <function my_deleter_func at 0x7f2f14b88ee0>
# my_getter_prop          = <property object at 0x7f2f14c7b180>
# my_setter_prop          = <property object at 0x7f2f14d318b0>
# my_deleter_prop         = <property object at 0x7f2f14c81e00>
# my_getter_prop          = <property object at 0x7f2f14c7b180>
# my_getter_prop.fget     = <function my_getter_func at 0x7f2f14a6d700>
# my_getter_prop.fset     = None
# my_getter_prop.fdel     = None
# my_getter_prop.__doc__  = ' getter doc'
# my_setter_prop          = <property object at 0x7f2f14d318b0>
# my_setter_prop.fget     = <function my_getter_func at 0x7f2f14a6d700>
# my_setter_prop.fset     = <function my_setter_func at 0x7f2f14a6d0d0>
# my_setter_prop.fdel     = None
# my_setter_prop.__doc__  = ' getter doc'
# my_deleter_prop         = <property object at 0x7f2f14c81e00>
# my_deleter_prop.fget    = <function my_getter_func at 0x7f2f14a6d700>
# my_deleter_prop.fset    = <function my_setter_func at 0x7f2f14a6d0d0>
# my_deleter_prop.fdel    = <function my_deleter_func at 0x7f2f14b88ee0>
# my_deleter_prop.__doc__ = ' getter doc'

# Create an empty type
class Husk:
    pass

# Assigning properties to the class itself is equivalent to how they are
# normally defined in the scope of the class definition.
Husk.x = my_deleter_prop
Husk.y = my_setter_prop
Husk.z = my_getter_prop


# Creating an instance of the class will let us use our property variables
self = Husk()

# The "deleter" property has fget, fset, and fdel defined
self.x
self.x = 3
del self.x


# The "setter" property only had fget and fset defined
self.y
self.y = 3
with pytest.raises(AttributeError):
    del self.y


# The "getter" property only had fget defined
self = Husk()
self.z
with pytest.raises(AttributeError):
    self.z = 3
    del self.z

These tests demonstrate how the docstring of the property defaults to that of the getter. So, I’m fairly confident ignoring the setters / deleters is the right approach for consistency with the dynamic parser.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Determine if a JavaScript property has a getter or setter ...
I only want to define a getter/setter if there is not already one defined on the property.
Read more >
Property getters and setters
Accessor properties are represented by “getter” and “setter” methods. In an object literal they are denoted by get and set :.
Read more >
3. Properties vs. Getters and Setters | OOP
The following example shows a class, which has internal attributes, which can't be accessed from outside. These are the private attributes self.
Read more >
What to do about setters of a different type than their ...
Consider this code: import typing class A: @property def f(self) -> int: return 1 @f.setter # Possible error for defining a setter that ......
Read more >
setter - JavaScript - MDN Web Docs
In JavaScript, a setter can be used to execute a function whenever a specified property is attempted to be changed. Setters are most...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found