V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
SimbaPeng
V2EX  ›  Python

我想知道 Python 的类中到底有没有建立作用域?

  •  
  •   SimbaPeng · 2017-11-06 00:58:56 +08:00 · 3940 次点击
    这是一个创建于 2363 天前的主题,其中的信息可能已经有所发展或是发生改变。
    以下是 py3 的代码:

    class A:
    ....a = 3
    ....def test():
    ........print(a)


    A.test()

    输出:name 'a' is not defined

    这个结果来看类中貌似是没有作用域的。

    class A:
    ....a = 3
    ....print(a)

    输出:3
    但是这个例子又能正常打印出本地变量 3,说明类中还是有局部作用域的?
    29 条回复    2017-11-07 01:11:27 +08:00
    wevsty
        1
    wevsty  
       2017-11-06 01:11:58 +08:00
    test 函数里应该使用 print(self.a)。
    Trim21
        2
    Trim21  
       2017-11-06 01:44:29 +08:00 via Android
    http://python.jobbole.com/81367/
    有是有,但是我没看懂类定义这里到底是怎么算得…
    takanasi
        3
    takanasi  
       2017-11-06 01:59:18 +08:00 via Android
    从来没想过直接访问类属性,又学到了无用的知识
    SimbaPeng
        4
    SimbaPeng  
    OP
       2017-11-06 02:06:34 +08:00
    @wevsty 这个我知道,但是如果类中存在作用域的话,按照 LEGB 是可以访问到 a 的
    zhy0216
        5
    zhy0216  
       2017-11-06 02:27:48 +08:00
    感觉是个 bug ???
    zhy0216
        6
    zhy0216  
       2017-11-06 02:41:06 +08:00
    这里有答案: https://www.python.org/dev/peps/pep-0227/#specification
    Names in class scope are not accessible

    不过我还没理解为什么这么设计?

    可能是这样 如果允许你这样写, 我们假设还有一个 global var: a,
    再运行 del A.a, 那这时候 test 的 a 是不是会指向 global 的 a
    那就成了 dynamic scope 了...

    可能是你这样写, python 无法再编译后就确定其 scope 必须在运行时才知道
    lrxiao
        7
    lrxiao  
       2017-11-06 03:52:57 +08:00
    因为
    class A:
    a = 3
    print(3)

    相当于
    def func_A():
    a = 3 # 3 in func_A().__code__.co_consts, a in func_A().__code__.co_names
    print(3)

    A = builtin.__build_class__(func_A, 'A') # 转移__code__中的 attribute
    lrxiao
        8
    lrxiao  
       2017-11-06 03:55:26 +08:00
    嘛 缩进全砸了。。这个东西和作用域没啥关系
    binux
        9
    binux  
       2017-11-06 05:07:25 +08:00 via Android
    我的理解是,类中的作用域是声明时的作用域,运行时不存在
    mec
        11
    mec  
       2017-11-06 09:41:33 +08:00
    方法没传入 cls 或者 self 啊
    ifkite
        12
    ifkite  
       2017-11-06 09:55:23 +08:00   ❤️ 1
    有作用域。可以改成这样。
    class A:
    ....a = 3
    ....def test():
    ........print(a)
    ....test()

    你的代码调方式不对,你的代码调用方式是类的方法的调用,需要函数需要用 staticmethod 或 classmethod 装饰。
    类中定义的方法 要么 staticmethod,要么 classmethod,要么定义实例的方法。
    SimbaPeng
        13
    SimbaPeng  
    OP
       2017-11-06 10:01:16 +08:00
    @ifkite 你这样写一样报 a 变量不存在的 error,在 py3 中不需要用 staticmethod 或 classmethod 装饰也能 A.test()这样调用的
    SimbaPeng
        14
    SimbaPeng  
    OP
       2017-11-06 10:03:40 +08:00
    @mec py3 中不传 cls,self 一样可以调用,这不是报错原因
    zhengxiaowai
        15
    zhengxiaowai  
       2017-11-06 10:41:55 +08:00
    Python 中变量可以分为:局部变量、全局变量、自有变量。

    Python 是有作用域 全局的 global() 和 本地的 locals() 可以得到。

    你用一个类举例子是不正确的。

    class A:
    ....a = 3
    ....def test():
    ........print(a)

    A.test()

    这个栗子肯定是有问题的。。。必然报错,我也无法重现这个错误。错也不是你那个错误,而是

    TypeError: unbound method test() must be called with A instance as first argument (got nothing instead)

    很明显 test() 是一个未绑定方法,具体什么是未绑定,可以参考 Fluent Python 中的相关章节。(具体哪一个忘记了。。)

    这个 Class A 中 a 叫做类属性,然而在 Python 类中要获得属性需要用 __new__ 返回的值,也就是 self.a 来获取。
    这就是为什么或有方法第一个参数都是 self 了。

    class A:
    ....a = 3
    ....print(a)

    这个例子能够输出是应为 print() 执行时在构建类时候,也就是在 __new__ 中执行。这时候 a 还是一个普通变量,不是一个属性。

    假设你的描述的都是正确的,那么就可以总结来说,第一个和第二个的差别在于执行的时期不同。当类生成以后,a 就变成类属性 需要通过 A.a 或者 self.a 来调用
    ipwx
        16
    ipwx  
       2017-11-06 10:47:58 +08:00
    请使用 Python 的范式来写 Python 的程序,不要把 C++/Java 世界的 style 拿到 Python 来用。

    Python 访问对象属性的方法只有一种,obj.XXX 。所以成员函数需要传入 self,然后用 self.XXX
    lrxiao
        17
    lrxiao  
       2017-11-06 10:57:56 +08:00
    讲 unbound 的都是 py2 选手。。
    py3 除非你要 instance 访问才需要 @staticmethod
    打印 3 是创建时的事情 (发现我上面写的疯狂笔误。。)
    SimbaPeng
        18
    SimbaPeng  
    OP
       2017-11-06 11:02:46 +08:00
    @zhengxiaowai
    首先我这个例子在 py3.6 中调用是肯定不会报你说的那个错误,你可以自己去试试。
    另外:
    class A:
    .... a = 3
    ....print(locals())

    输出:{'__module__': '__main__', '__qualname__': 'A', 'a': 3}
    说明在 class 定义时候是有局部作用域产生的.

    如果说执行时间不同,那么这样写应该可以找到 a 吧:
    class A:
    ....a = 3
    ....def test():
    ........print(a)
    ....test()

    输出: NameError: name 'a' is not defined

    一样是找不到 a 变量,我估计是类的作用域和类中的方法不是嵌套关系
    lrxiao
        19
    lrxiao  
       2017-11-06 11:08:59 +08:00
    这个 a 只是个 local variable 改下 @ifkite 的类

    import builtins
    import sys


    old_bc = builtins.__build_class__

    def fake_build_class(cls_func, cls_name):
    ....print("Build class...")
    ....print(cls_func.__code__.co_consts)
    ....print(cls_func.__code__.co_names)
    ....print(cls_name)
    ....print("Create class...")
    ....cls = old_bc(cls_func, cls_name)
    ....print("End of create class...")
    ....print(cls.__dict__['a'])

    builtins.__build_class__ = fake_build_class

    class A:
    ....a = 3
    ....def test():
    ........frame = sys._getframe(1)
    ........print(frame.f_locals['a'])
    ....test()

    嗯 极其丧病
    hcnhcn012
        20
    hcnhcn012  
       2017-11-06 12:24:26 +08:00 via iPhone
    好问题啊。。。我到现在都没搞清楚和模块级作用域的区别
    hcnhcn012
        21
    hcnhcn012  
       2017-11-06 12:40:46 +08:00 via iPhone
    @zhengxiaowai 那为什么类里面不弄个只关于当前类内部的 global,nonlocal 和 local 的作用域呢,这样感觉更加清楚了啊,比如调用类属性就不用 classmethod 或 staticmethod 了,这样不是更像一个模块了嘛,一直不懂这个设计。。。
    ifkite
        22
    ifkite  
       2017-11-06 14:57:56 +08:00
    @SimbaPeng
    额,测了下确实会报 a 变量不存在的 error。
    改成这样呢?
    class A(object):
    ....a = 3
    ....def func():
    ........print 'i am in test:', locals()
    ........print A.a
    ....print 'i am in A:', locals()
    ....func()
    ....print 'i am in A:', locals()
    focusheart
        23
    focusheart  
       2017-11-06 16:58:40 +08:00
    @ipwx 支持。
    Gesamtkunstwerk
        24
    Gesamtkunstwerk  
       2017-11-06 19:22:53 +08:00
    楼上大牛们扯一堆表示萌新并看不懂,难道不是实例化的问题?
    先把 A 实例化,实例化对象再调用 test (),而且你函数写的也不对,应该 def test ( self ):print ( self.a )
    luckins
        25
    luckins  
       2017-11-06 19:53:24 +08:00
    既没有实例化,也没用正确的方式调用全局变量
    ob
        26
    ob  
       2017-11-06 19:59:35 +08:00 via Android
    萌新表示看不懂这个 test 是类方法吗?如果不是怎么可以直接调用呢?不是应该先实例化后才能调?
    laqow
        27
    laqow  
       2017-11-06 20:48:13 +08:00 via Android
    个人认为 python 类的变量不是楼主这样定义和赋值的,应该是在__init__(self)里面定义,这样在形式上能让人理解变量是干嘛用的,也不会让编译器误解。python 类本身也是一个对象,和数组,函数,字符串甚至它自己的实例都没有区别,不按规范写你把它当个对象用也可以。
    l1093178
        28
    l1093178  
       2017-11-07 01:03:41 +08:00
    TL; DR 类在定义的时候是有作用域的,这个作用域内的变量只能在 class 块内访问,而不能在类的方法(函数)中访问。

    这种问题当然应该是去看 Language Reference 啦

    Python 3: https://docs.python.org/3/reference/executionmodel.html#naming-and-binding

    > The following constructs bind names: formal parameters to functions, import statements, class and function definitions (these bind the class or function name in the defining block) ...

    然后

    > 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.
    l1093178
        29
    l1093178  
       2017-11-07 01:11:27 +08:00
    至于为什么要这么设计,估计就是一些非常奇怪的历史性问题了,@zhy0216 提到的 PEP-227 也许值得一读
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1019 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 108ms · UTC 19:36 · PVG 03:36 · LAX 12:36 · JFK 15:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.