1

I got a very strange behavior, where I can't use global variable as default value if a property has the same name, e.g.:

import datetime as dt


class A:
    @property
    def dt(self):
        return 1

    def foo(self, dt_=dt.datetime.min):
        print(dt_ > dt.datetime.now())
        pass

This gives an error:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    class A:
  File "tmp.py", line 9, in A
    def foo(self, dt_=dt.datetime.min):
AttributeError: 'property' object has no attribute 'datetime'

This seems counter-intuitive to me, why would a property override a global variable, but only in default assignment? dt works properly inside the method (can check by using a different default value).

What am I missing?

I checked on clean env of 3.8 and 3.10, so it doesn't seem to be a test version issue. I use python provided by conda.

Rizhiy
  • 775
  • 1
  • 9
  • 31
  • 3
    This is kind of weird one, but there is a "temporary" scope used during the class definition block, and the default arguments in that case are resolved in that scope (during class definition time). It doesn't happen inside the method because you are not in the scope of the temporary class block at that stage. [UnboundLocalError: local variable referenced before assignment why LEGB Rule not applied in this case](https://stackoverflow.com/a/45195215/674039) has some details, including a docs reference for this strange behavior (TL;DR- not a bug, it's as designed). – wim Sep 07 '22 at 02:58
  • See also (possibly duplicate): [What is a keyword argument value's namespace in python?](https://stackoverflow.com/q/58313496/674039) – wim Sep 07 '22 at 03:02
  • Just to be clear, it's not just the default argument that's affected. You could also do something like `some_attr = dt` at the end of `class A`, and that `dt` would also resolve to the property instead of the import. – wjandrea Sep 07 '22 at 03:02
  • I tested and the code is OK, so is something related with the scope. – Luis Alejandro Vargas Ramos Sep 07 '22 at 03:03
  • @Luis Sorry, what do you mean "the code is OK"? Did it not raise an error when you ran it? Because it should. That'd be strange if it didn't. – wjandrea Sep 07 '22 at 03:04
  • I mean, the function is Ok, is doing what it means to do. – Luis Alejandro Vargas Ramos Sep 07 '22 at 03:04
  • @Luis Oh, OK, yeah, OP's already aware of that. Like they wrote, "can check by using a different default value". – wjandrea Sep 07 '22 at 03:05
  • dt you defined in class will be the one using in that class. and it dont have datetime property. this one regarding to the scope. the one in the function will be first prority – Henro Sutrisno Tanjung Sep 07 '22 at 03:07
  • @wim OK, that makes sense. If you post it as an answer, I will accept it, if not, I guess I will explain it myself later. Always found class scope confusing in python, I guess another thing I learned today. – Rizhiy Sep 07 '22 at 03:12
  • @Rizhiy OK, I've added an answer more focused to this question, since it is not exactly a duplicate question although the answers may be similar/same. – wim Sep 07 '22 at 03:21

1 Answers1

1

The class definition block creates a "temporary" scope, and the default arguments of methods are evaluated within this scope (which ceases to exist once the class has been defined).

Actually, the property is not necessary, here is a simpler way to demonstrate the same behavior:

>>> x = "val_outer"
>>> class A:
...     x = "val_class"
...     def foo(self, k=x):
...         print(k)
...         print(x)
... 
>>> A().foo()
val_class
val_outer

As mentioned in other answers linked in the comments, a reference for the weird class block scoping is in the execution model docs, section 4.2.2 Resolution of names:

Class definition blocks and arguments to exec() and eval() are special in the context of name resolution. A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace. The namespace of the class definition becomes the attribute dictionary of the class. The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope.

wim
  • 338,267
  • 99
  • 616
  • 750