Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not raise AttributeError on instance attribute update/deletion if data descriptor with missing __set__/__delete__ method found on its type #87805

Closed
maggyero mannequin opened this issue Mar 27, 2021 · 2 comments
Assignees
Labels
3.10 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@maggyero
Copy link
Mannequin

maggyero mannequin commented Mar 27, 2021

BPO 43639
Nosy @rhettinger, @maggyero
PRs
  • bpo-43639: Do not raise AttributeError on instance attribute update/deletion if data descriptor with missing __set__/__delete__ method found on its type #25033
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/rhettinger'
    closed_at = <Date 2021-12-29.09:19:39.583>
    created_at = <Date 2021-03-27.12:40:32.176>
    labels = ['interpreter-core', 'type-bug', '3.10']
    title = 'Do not raise AttributeError on instance attribute update/deletion if data descriptor with missing __set__/__delete__ method found on its type'
    updated_at = <Date 2021-12-29.09:19:49.485>
    user = 'https://github.com/maggyero'

    bugs.python.org fields:

    activity = <Date 2021-12-29.09:19:49.485>
    actor = 'rhettinger'
    assignee = 'rhettinger'
    closed = True
    closed_date = <Date 2021-12-29.09:19:39.583>
    closer = 'rhettinger'
    components = ['Interpreter Core']
    creation = <Date 2021-03-27.12:40:32.176>
    creator = 'maggyero'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 43639
    keywords = ['patch']
    message_count = 2.0
    messages = ['389598', '409297']
    nosy_count = 2.0
    nosy_names = ['rhettinger', 'maggyero']
    pr_nums = ['25033']
    priority = 'normal'
    resolution = 'rejected'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue43639'
    versions = ['Python 3.10']

    @maggyero
    Copy link
    Mannequin Author

    maggyero mannequin commented Mar 27, 2021

    Currently, the object.__setattr__ and type.__setattr__ methods raise an AttributeError during attribute update on an instance if its type has an attribute which is a data descriptor without a __set__ method. Likewise, the object.__delattr__ and type.__delattr__ methods raise an AttributeError during attribute deletion on an instance if its type has an attribute which is a data descriptor without a __delete__ method.

    This should not be the case. When update/deletion is impossible through a data descriptor found on the type, update/deletion should carry its process on the instance, like when there is no data descriptor found on the type. And this is what the object.__getattribute__ and type.__getattribute__ methods already do: they do not raise an AttributeError during attribute lookup on an instance if its type has an attribute which is a data descriptor without a __get__ method. See the discussion on Python Discuss.

    Here is a simple program illustrating the differences between attribute lookup by object.__getattribute__ on the one hand (AttributeError is not raised), and attribute update by object.__setattr__ and attribute deletion by object.__delattr__ on the other hand (AttributeError is raised):

    class DataDescriptor1:  # missing __get__
        def __set__(self, instance, value): pass
        def __delete__(self, instance): pass
    
    class DataDescriptor2:  # missing __set__
        def __get__(self, instance, owner=None): pass
        def __delete__(self, instance): pass
    
    class DataDescriptor3:  # missing __delete__
        def __get__(self, instance, owner=None): pass
        def __set__(self, instance, value): pass
    
    class A:
        x = DataDescriptor1()
        y = DataDescriptor2()
        z = DataDescriptor3()
    
    a = A()
    vars(a).update({'x': 'foo', 'y': 'bar', 'z': 'baz'})
    
    a.x
    # actual: returns 'foo'
    # expected: returns 'foo'
    
    a.y = 'qux'
    # actual: raises AttributeError: __set__
    # expected: vars(a)['y'] == 'qux'
    
    del a.z
    # actual: raises AttributeError: __delete__
    # expected: 'z' not in vars(a)

    Here is another simple program illustrating the differences between attribute lookup by type.__getattribute__ on the one hand (AttributeError is not raised), and attribute update by type.__setattr__ and attribute deletion by type.__delattr__ on the other hand (AttributeError is raised):

    class DataDescriptor1:  # missing __get__
        def __set__(self, instance, value): pass
        def __delete__(self, instance): pass
    
    class DataDescriptor2:  # missing __set__
        def __get__(self, instance, owner=None): pass
        def __delete__(self, instance): pass
    
    class DataDescriptor3:  # missing __delete__
        def __get__(self, instance, owner=None): pass
        def __set__(self, instance, value): pass
    
    class M(type):
        x = DataDescriptor1()
        y = DataDescriptor2()
        z = DataDescriptor3()
    
    class A(metaclass=M):
        x = 'foo'
        y = 'bar'
        z = 'baz'
    
    A.x
    # actual: returns 'foo'
    # expected: returns 'foo'
    
    A.y = 'qux'
    # actual: raises AttributeError: __set__
    # expected: vars(A)['y'] == 'qux'
    
    del A.z
    # actual: raises AttributeError: __delete__
    # expected: 'z' not in vars(A)

    @maggyero maggyero mannequin added 3.7 (EOL) end of life 3.8 only security fixes 3.9 only security fixes 3.10 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error labels Mar 27, 2021
    @rhettinger rhettinger removed 3.7 (EOL) end of life 3.8 only security fixes 3.9 only security fixes labels Mar 27, 2021
    @rhettinger
    Copy link
    Contributor

    I'm going to decline this one.

    • It is arguable whether or not this behavior should have been designed in originally. However, changing it now is risky (as noted by Brett and Ethan).

    • Personally, I disagree with the notion of allowing a partial pass through. That seems hazardous and error-prone to me. It is easier to reason about data descriptors being all or nothing. I like that AttributeError is raised while still allowing me to add the missing methods if I want to explicitly define some other behavior.

    • This has been open for 9 months and no one else stepped forward to champion it.

    • For two decades, no one has complained that the current behavior got in the way of what they were trying to do. That provides some evidence that there isn't a real world problem to be solved.

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.10 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    1 participant