Eoferror ran out of input ошибка

Short answer: The simplest solution is to write the complete list to file using pickle.dump(). There’s no need to write all objects one by one in a loop. Pickle is designed to do this for you.

Example code and alternative solutions:

Below is a fully working example. Some notes:

  • I’ve updated your __init__ function a bit to make the initialization code a lot easier and shorter.
  • I’ve also added a __repr__ function. This could be used to print the record details to screen, which you also asked. (Note that you could also implement a __str__ function, but I chose to implement __repr__ for this example).
  • This code example uses standard Python coding styles (PEP-8).
  • This code uses a context manager to open the file. This is safer and avoid the need to manually close the file.

If you really want to write the objects manually, for whatever reason, there are a few alternatives to do that safely. I’ll explain them after this code example:

import pickle


class CarRecord:

    def __init__(self, vehicle_id, registration, registration_date, engine_size, purchase_price):
        self.vehicle_id = vehicle_id
        self.registration = registration
        self.registration_date = registration_date
        self.engine_size = engine_size
        self.purchase_price = purchase_price

    def __repr__(self):
        return "CarRecord(%r, %r, %r, %r, %r)" % (self.vehicle_id, self.registration,
                                                  self.registration_date, self.engine_size,
                                                  self.purchase_price)


def main():
    cars = [
        CarRecord("CD333", "17888", "18/2/2017", 2500, 22000.00),
        CarRecord("AB123", "16988", "19/2/2017", 2500, 20000.00),
    ]

    # Write cars to file.
    with open('Cars.TXT', 'wb') as car_file:
        pickle.dump(cars, car_file)

    # Read cars from file.
    with open('Cars.TXT', 'rb') as car_file:
        cars = pickle.load(car_file)

    # Print cars.
    for car in cars:
        print(car)


if __name__ == '__main__':
    main()

Output:

CarRecord('CD333', '17888', '18/2/2017', 2500, 22000.0)
CarRecord('AB123', '16988', '19/2/2017', 2500, 20000.0)

Instead of dumping the list at once, you could also do it in a loop. The following code snippets are alternative implementations to «Write cars to file» and «Read cars from file».

Alternative 1: write number of objects to file

At the start of the file, write the number of cars. This can be used to read the same amount of cars from the file.

    # Write cars to file.
    with open('Cars.TXT', 'wb') as car_file:
        pickle.dump(len(cars), car_file)
        for car in cars:
            pickle.dump(car, car_file)

    # Read cars from file.
    with open('Cars.TXT', 'rb') as car_file:
        num_cars = pickle.load(car_file)
        cars = [pickle.load(car_file) for _ in range(num_cars)]

Alternative 2: use an «end» marker

At the end of the file, write some recognizable value, for example None. When reading this object can be used to detect the end of file.

    # Write cars to file.
    with open('Cars.TXT', 'wb') as car_file:
        for car in cars:
            pickle.dump(car, car_file)
        pickle.dump(None, car_file)

    # Read cars from file.
    with open('Cars.TXT', 'rb') as car_file:
        cars = []
        while True:
            car = pickle.load(car_file)
            if car is None:
                break
            cars.append(car)

Eoferror: ran out of input” is an error that occurs during the programming process. It generally happens because the file is empty and has no input.the eoferror ran out of input

Thus, instead of finishing the program the usual way, it shows an eoferror error message. In this guide, directions and solutions to troubleshoot this issue have been discussed.

Contents

  • Why Does an Eoferror: Ran Out of Input Error Occur?
    • – The File Is Empty
    • – Using Unnecessary Functions in the Program
    • – Overwrite a Pickle File
    • – Using an Unknown File Name
    • – Using an Incorrect Syntax
  • How To Fix Eoferror: Ran Out of Input Error?
    • – Use an Additional Command To Show That the File Is Empty
    • – Avoid Using an Unnecessary Function in the Program
    • – Avoid Overwriting a Pickle File
    • – Do Not Use an Unknown Filename
    • – Use the Correct Syntax
  • FAQs
    • 1. How To Fix Eoferror Eof When Reading a Line in Python Without Errors?
    • 2. How To Read a Pickle File in Python To Avoid an Eoferror Error?
  • Conclusion

Why Does an Eoferror: Ran Out of Input Error Occur?

The eoferror ran out of input error occurs mainly because a program calls out an empty file. Some other causes include:

  • The file is empty.
  • Using unnecessary functions in the program.
  • Overwrite a pickle file.
  • Using an unknown filename.
  • Using an incorrect syntax: df=pickle.load(open(‘df.p’,’rb’))

– The File Is Empty

When a file is empty and has no input details in it, the program error occurs when that file is called out. It is also known as a pickle.load eoferror error.

Given below are a quick program and its output that explains the cause of the error.

Program:

Open(target, ‘c’).close()

scores = {};

with open(target, “rb”) as file:

unpickler = pickle.Unpickler(file);

scores = unpickler.load();

if not isinstance(scores, dict):

scores = {};

Output:

Traceback (most recent call last):

File “G:pythonpenduuser_test.py”, line 3, in :

save_user_points(“Magixs”, 31);

File “G:pythonpenduuser.py”, line 22, in save_user_points:

scores = unpickler.load();

EOFError: Ran out of input

Now, the reason why this error occurred was that the program called an empty file, and no other command was given.

– Using Unnecessary Functions in the Program

Sometimes, using an unnecessary function in the program can lead to undefined behavior in the output or errors such as EOFError: Ran out of the input.

Therefore, avoid using functions that are not required. Here’s the same example from above for avoiding confusion:

Open(target, ‘c’).close()

scores = {};

with open(target, “rb”) as file:

unpickler = pickle.Unpickler(file);

scores = unpickler.load();

if not isinstance(scores, dict):

scores = {};

– Overwrite a Pickle File

Sometimes, an empty pickle file can come as a surprise. This is because the programmer may have opened the filename through ‘bw’ or some other mode that could have overwritten the file.Eoferror Ran Out of Input Causes

Here’s an example of overwritten filename program:

filename = ‘do.pkl’

with open(filename, ‘bw’) as g:

classification_dict = pickle.load(g)

The function “classification_dict = pickle.load(g)” will overwrite the pickled file. This type of error can be made by mistake before using:

open(filename, ‘rb’) as g

Now, due to this, the programmer will get an EOFError because the previous block of code overwrote the do.pkl file.

– Using an Unknown File Name

This error occurs when the program has a filename that was not recognized earlier. Addressing an unrecognized filename in the middle of the program can cause various programming errors, including the “eoferror” error.

Eoferror has various types depending on the programming language and syntax. Some of them are:

  • Eoferror: ran out of input pandas.
  • Eoferror: ran out of input PyTorch.
  • Eoferror: ran out of input yolov5.

– Using an Incorrect Syntax

When typing a program, one has to be careful with the usage of the syntax. Wrong functions at the wrong time are also included in syntax errors. Sometimes overwriting filenames can cause syntax errors. Here’s a quick example of writing a pickle file:

pickle.dump(dg,open(‘dg.a’,’hw’))

However, if the programmer copied this code to reopen it but forgot to change ‘wb’ to ‘rb’, then that can cause overwriting syntax error:

dg=pickle.load(open(‘dg.a’,’hw’))

There are multiple ways in which this error can be resolved. Some common ways to fix this issue include using an additional command to show that the file is empty, avoiding unnecessary functions in the program and refraining from overwriting the pickle file.



A well-known way to fix this issue is by setting a condition in case the file is empty. If the condition is not included in the coding, an eoferror error will occur. The “ran out of input” means the end of a file and that it is empty.

– Use an Additional Command To Show That the File Is Empty

While typing a program and addressing a file, use an additional command that does not cause any error in the output due to an empty file. The error “eoferror ran out of input” can be easily fixed by doing what is recommended.

Let’s use the same example given above at the start. To fix that error, here’s how the program should have been written:

import os

scores = {}

if os.path.getsize(target) > 0:

with open(target, “cr”) as h:

unpickler = pickle.Unpickler(h)

# if the file is not empty, scores will be equal

# to the value unpickled

scores = unpickler.load()

– Avoid Using an Unnecessary Function in the Program

As the heading explains itself, do not use unnecessary functions while coding because it can confuse the programmer, thus causing eoferror error in the output.

The top line containing, the “Open(target, ‘a’).close()” function is not necessary in the program given in the section “using unnecessary function in the program”. Thus, it can cause issues or confusion to programmers while typing codes.

– Avoid Overwriting a Pickle File

Another way to remove the program’s eoferror errors is by rechecking and correcting the overwritten pickle file. The programmer can also try to avoid overwriting files using different techniques.Eoferror Ran Out of Input Fixes

It is recommended to keep a note of the files the programmer will be using in the program to avoid confusion. Previously, an example was given in the section, “Using an incorrect syntax”, so keeping that in mind, be careful with the overwriting of files.

Here is the example with the correct syntax to avoid overwriting:

pickle.dump(dg,open(‘dg.e’,’gb’))

dg=pickle.load(open(‘dg.e’,’gb’))

This has caused an overwriting issue. The correct way is:

dg=pickle.load(open(‘dg.e’,’ub’))

– Do Not Use an Unknown Filename

Before calling out any filename, it must be registered in the library while programming so that the user may have desired output when it is called. However, it is considered an unknown filename if it is not registered and the file has been called out.

Calling an unknown filename causes an eoferror error message in your developing platform. The user will be unable to get the desired output and will end up stuck in this error. For example, the user entered two filenames in the program, but only one is registered, and the other isn’t.

Let’s take “gd” as a registered filename and “ar” as a non-registered filename (unknown). Therefore:

import os

scores = {} # scores is an empty dict already

if os.path.getsize(target) > 0:

with open(target, “ar”) as g:

unpickler = pickle.Unpickler(g)

# if the file is not empty, scores will be equal

# to the value unpickled

scores = unpickler.load()

As seen above, the filename used here is unknown to the program. Thus, the output of this program will include errors. So, make sure the file is registered.

– Use the Correct Syntax

This is another common reason for the eoferror input error while typing a program in the python programming language. Therefore, an easy way to resolve this is by taking a few notes.

Before starting the coding process, search and note down the basic syntaxes of that particular program, such as Python, Java and C++ etc.

Doing so will help beginners and make it significantly easy for them to avoid syntax errors while coding. Make sure to enter the correct syntax and do not overwrite, as that too can cause syntax errors.

FAQs

1. How To Fix Eoferror Eof When Reading a Line in Python Without Errors?

The most common reason behind Eoferror is that you have reached the end of the file without reading all the data. To fix this error, make sure to read all the data in the file before trying to access its contents. It can be done by using a loop to read through the file’s contents.

2. How To Read a Pickle File in Python To Avoid an Eoferror Error?

The programmer can use the pandas library to read a pickle file in Python. The pandas module has a read_pickle() method that can be used to read a pickle file. By using this, one can avoid the eoferror empty files issue.

This is because the pandas library in Python detects such errors beforehand. Resulting in a much smoother programming experience.

Conclusion

After reading this article thoroughly, the reader will be able to do their programming much more quickly because they’ll know why the eoferror ran out of input error messages. Here is a quick recap of this guide:

  • The eoferror error usually occurs when the file is empty or the filename is accidentally overwritten.
  • The best way to avoid errors like eoferror is by correcting the syntax
  • Ensure that before calling the pickled file, the program should also have an alternative command in case the pickled file is empty and has no input in it.
  • When working in Jupyter, or the console (Spyder), write a wrapper over the reading/writing code and call the wrapper subsequently.

The reader can now tactfully handle this error and continue doing their programming efficiently, and if you have some difficulty, feel free to come back to read this guide. Thank you for reading!

  • Author
  • Recent Posts

Position is Everything

Your Go-To Resource for Learn & Build: CSS,JavaScript,HTML,PHP,C++ and MYSQL. Meet The Team

Position is Everything

Уведомления

  • Начало
  • » Python для новичков
  • » Ran out of input

#1 Март 30, 2016 13:59:14

Ran out of input

def top_scores(score):
    """Forms a top scores list"""
    name = input("Enter your name")
    
    f = open("pickles11.dat", "wb+")
    top_scores = pickle.load(f)
    top_scores[score] = name
    pickle.dump(top_scores, f)
    f.close()
    keys = top_scores.keys()
    keys = list(keys)
    keys.sort(reverse=True)
    print("nTop scores:")
    
    for i in keys:
        print(top_scores[i], i)

При выполнении возникает ошибка:
top_scores = pickle.load(f)
EOFError: Ran out of input

Подскажите, пожалуйста, где здесь проблема.

Офлайн

  • Пожаловаться

#2 Март 30, 2016 14:56:55

Ran out of input

Stranger

f = open("pickles11.dat", "wb+")

wb+ стирает содержимое файла.

Онлайн

  • Пожаловаться

#3 Март 30, 2016 15:02:59

Ran out of input

Сначала получаем значение, потом его изменяем и вновь записываем, wb+ это позволяет. Я не прав?

P.S. Да и в принципе не важно, что оно делает, просто подскажите, как сделать, чтобы всё заработало

Отредактировано Stranger (Март 30, 2016 15:06:38)

Прикреплённый файлы:
attachment Снимок экрана от 2016-03-30 15-00-47.png (212,3 KБ)

Офлайн

  • Пожаловаться

#5 Март 30, 2016 15:25:37

Ran out of input

Ну так в файл перезаписывается уже полученная и измененная ценность, или я сильно ошибаюсь?

Офлайн

  • Пожаловаться

#6 Март 30, 2016 15:31:15

Ran out of input

Stranger
Откройте файл с wb+ и прочтитайте его содержимое.
Согласитесь, проще сделать это, чем гадать?

Офлайн

  • Пожаловаться

#7 Март 30, 2016 15:47:54

Ran out of input

def top_scores(score):
    """Forms a top scores list"""
    name = input("Enter your name")
    f = open("pickles11.dat", "rb")
    top_scores = pickle.load(f)
    top_scores[score] = name
    f.close()
    f = open("pickles11.dat", "wb")
    pickle.dump(top_scores, f)
    f.close()
    keys = top_scores.keys()
    keys = list(keys)
    keys.sort(reverse=True)
    print("nTop scores:")
    
    for i in keys:
        print(top_scores[i], i)

Да, вы правы, спасибо за помощь. Переписал, всё работает.

Офлайн

  • Пожаловаться

I am a tensorflow noob. When I run «./experiments/scripts/train_faster_rcnn.sh 0 pascal_voc vgg16», some issues occurs, just like below:

Writing mine VOC results file
VOC07 metric? Yes
Traceback (most recent call last):
File «/media/tf/MyFiles/codes/tf-faster-rcnn/tools/../lib/datasets/voc_eval.py», line 126, in voc_eval
recs = pickle.load(f)
EOFError: Ran out of input

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File «./tools/test_net.py», line 120, in
test_net(sess, net, imdb, filename, max_per_image=args.max_per_image)
File «/media/tf/MyFiles/codes/tf-faster-rcnn/tools/../lib/model/test.py», line 193, in test_net
imdb.evaluate_detections(all_boxes, output_dir)
File «/media/tf/MyFiles/codes/tf-faster-rcnn/tools/../lib/datasets/pascal_voc.py», line 287, in evaluate_detections
self._do_python_eval(output_dir)
File «/media/tf/MyFiles/codes/tf-faster-rcnn/tools/../lib/datasets/pascal_voc.py», line 250, in _do_python_eval
use_07_metric=use_07_metric)
File «/media/tf/MyFiles/codes/tf-faster-rcnn/tools/../lib/datasets/voc_eval.py», line 128, in voc_eval
recs = pickle.load(f, encoding=’bytes’)
EOFError: Ran out of input
Command exited with non-zero status 1
7.86user 0.96system 0:09.05elapsed 97%CPU (0avgtext+0avgdata 1956200maxresident)k
0inputs+56outputs (0major+355880minor)pagefaults 0swaps

I need help,and is there anyone can give me some solutions?Thanks a lot!

Classes as objects

Before understanding metaclasses, you need to master classes in Python. And Python has a very peculiar idea of what classes are, borrowed from the Smalltalk language.

In most languages, classes are just pieces of code that describe how to produce an object. That’s kinda true in Python too:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

But classes are more than that in Python. Classes are objects too.

Yes, objects.

As soon as you use the keyword class, Python executes it and creates
an object. The instruction

>>> class ObjectCreator(object):
...       pass
...

creates in memory an object with the name ObjectCreator.

This object (the class) is itself capable of creating objects (the instances),
and this is why it’s a class
.

But still, it’s an object, and therefore:

  • you can assign it to a variable
  • you can copy it
  • you can add attributes to it
  • you can pass it as a function parameter

e.g.:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Creating classes dynamically

Since classes are objects, you can create them on the fly, like any object.

First, you can create a class in a function using class:

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

But it’s not so dynamic, since you still have to write the whole class yourself.

Since classes are objects, they must be generated by something.

When you use the class keyword, Python creates this object automatically. But as
with most things in Python, it gives you a way to do it manually.

Remember the function type? The good old function that lets you know what
type an object is:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

Well, type has a completely different ability, it can also create classes on the fly. type can take the description of a class as parameters,
and return a class.

(I know, it’s silly that the same function can have two completely different uses according to the parameters you pass to it. It’s an issue due to backward
compatibility in Python)

type works this way:

type(name, bases, attrs)

Where:

  • name: name of the class
  • bases: tuple of the parent class (for inheritance, can be empty)
  • attrs: dictionary containing attributes names and values

e.g.:

>>> class MyShinyClass(object):
...       pass

can be created manually this way:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

You’ll notice that we use MyShinyClass as the name of the class
and as the variable to hold the class reference. They can be different,
but there is no reason to complicate things.

type accepts a dictionary to define the attributes of the class. So:

>>> class Foo(object):
...       bar = True

Can be translated to:

>>> Foo = type('Foo', (), {'bar':True})

And used as a normal class:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

And of course, you can inherit from it, so:

>>>   class FooChild(Foo):
...         pass

would be:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Eventually, you’ll want to add methods to your class. Just define a function
with the proper signature and assign it as an attribute.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

And you can add even more methods after you dynamically create the class, just like adding methods to a normally created class object.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

You see where we are going: in Python, classes are objects, and you can create a class on the fly, dynamically.

This is what Python does when you use the keyword class, and it does so by using a metaclass.

Metaclasses are the ‘stuff’ that creates classes.

You define classes in order to create objects, right?

But we learned that Python classes are objects.

Well, metaclasses are what create these objects. They are the classes’ classes,
you can picture them this way:

MyClass = MetaClass()
my_object = MyClass()

You’ve seen that type lets you do something like this:

MyClass = type('MyClass', (), {})

It’s because the function type is in fact a metaclass. type is the
metaclass Python uses to create all classes behind the scenes.

Now you wonder «why the heck is it written in lowercase, and not Type

Well, I guess it’s a matter of consistency with str, the class that creates
strings objects, and int the class that creates integer objects. type is
just the class that creates class objects.

You see that by checking the __class__ attribute.

Everything, and I mean everything, is an object in Python. That includes integers,
strings, functions and classes. All of them are objects. And all of them have
been created from a class:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Now, what is the __class__ of any __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

So, a metaclass is just the stuff that creates class objects.

You can call it a ‘class factory’ if you wish.

type is the built-in metaclass Python uses, but of course, you can create your
own metaclass.

In Python 2, you can add a __metaclass__ attribute when you write a class (see next section for the Python 3 syntax):

class Foo(object):
    __metaclass__ = something...
    [...]

If you do so, Python will use the metaclass to create the class Foo.

Careful, it’s tricky.

You write class Foo(object) first, but the class object Foo is not created
in memory yet.

Python will look for __metaclass__ in the class definition. If it finds it,
it will use it to create the object class Foo. If it doesn’t, it will use
type to create the class.

Read that several times.

When you do:

class Foo(Bar):
    pass

Python does the following:

Is there a __metaclass__ attribute in Foo?

If yes, create in-memory a class object (I said a class object, stay with me here), with the name Foo by using what is in __metaclass__.

If Python can’t find __metaclass__, it will look for a __metaclass__ at the MODULE level, and try to do the same (but only for classes that don’t inherit anything, basically old-style classes).

Then if it can’t find any __metaclass__ at all, it will use the Bar‘s (the first parent) own metaclass (which might be the default type) to create the class object.

Be careful here that the __metaclass__ attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a __metaclass__ attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior.

Now the big question is, what can you put in __metaclass__?

The answer is something that can create a class.

And what can create a class? type, or anything that subclasses or uses it.

The syntax to set the metaclass has been changed in Python 3:

class Foo(object, metaclass=something):
    ...

i.e. the __metaclass__ attribute is no longer used, in favor of a keyword argument in the list of base classes.

The behavior of metaclasses however stays largely the same.

One thing added to metaclasses in Python 3 is that you can also pass attributes as keyword-arguments into a metaclass, like so:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Read the section below for how Python handles this.

The main purpose of a metaclass is to change the class automatically,
when it’s created.

You usually do this for APIs, where you want to create classes matching the
current context.

Imagine a stupid example, where you decide that all classes in your module
should have their attributes written in uppercase. There are several ways to
do this, but one way is to set __metaclass__ at the module level.

This way, all classes of this module will be created using this metaclass,
and we just have to tell the metaclass to turn all attributes to uppercase.

Luckily, __metaclass__ can actually be any callable, it doesn’t need to be a
formal class (I know, something with ‘class’ in its name doesn’t need to be
a class, go figure… but it’s helpful).

So we will start with a simple example, by using a function.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """
    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attrs)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

Let’s check:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

Now, let’s do exactly the same, but using a real class for a metaclass:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Let’s rewrite the above, but with shorter and more realistic variable names now that we know what they mean:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

You may have noticed the extra argument cls. There is
nothing special about it: __new__ always receives the class it’s defined in, as the first parameter. Just like you have self for ordinary methods which receive the instance as the first parameter, or the defining class for class methods.

But this is not proper OOP. We are calling type directly and we aren’t overriding or calling the parent’s __new__. Let’s do that instead:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

We can make it even cleaner by using super, which will ease inheritance (because yes, you can have metaclasses, inheriting from metaclasses, inheriting from type):

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

Oh, and in Python 3 if you do this call with keyword arguments, like this:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

It translates to this in the metaclass to use it:

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

That’s it. There is really nothing more about metaclasses.

The reason behind the complexity of the code using metaclasses is not because
of metaclasses, it’s because you usually use metaclasses to do twisted stuff
relying on introspection, manipulating inheritance, vars such as __dict__, etc.

Indeed, metaclasses are especially useful to do black magic, and therefore
complicated stuff. But by themselves, they are simple:

  • intercept a class creation
  • modify the class
  • return the modified class

Since __metaclass__ can accept any callable, why would you use a class
since it’s obviously more complicated?

There are several reasons to do so:

  • The intention is clear. When you read UpperAttrMetaclass(type), you know
    what’s going to follow
  • You can use OOP. Metaclass can inherit from metaclass, override parent methods. Metaclasses can even use metaclasses.
  • Subclasses of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.
  • You can structure your code better. You never use metaclasses for something as trivial as the above example. It’s usually for something complicated. Having the ability to make several methods and group them in one class is very useful to make the code easier to read.
  • You can hook on __new__, __init__ and __call__. Which will allow you to do different stuff, Even if usually you can do it all in __new__,
    some people are just more comfortable using __init__.
  • These are called metaclasses, damn it! It must mean something!

Now the big question. Why would you use some obscure error-prone feature?

Well, usually you don’t:

Metaclasses are deeper magic that
99% of users should never worry about it.
If you wonder whether you need them,
you don’t (the people who actually
need them to know with certainty that
they need them and don’t need an
explanation about why).

Python Guru Tim Peters

The main use case for a metaclass is creating an API. A typical example of this is the Django ORM. It allows you to define something like this:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

But if you do this:

person = Person(name='bob', age='35')
print(person.age)

It won’t return an IntegerField object. It will return an int, and can even take it directly from the database.

This is possible because models.Model defines __metaclass__ and
it uses some magic that will turn the Person you just defined with simple statements
into a complex hook to a database field.

Django makes something complex look simple by exposing a simple API
and using metaclasses, recreating code from this API to do the real job
behind the scenes.

The last word

First, you know that classes are objects that can create instances.

Well, in fact, classes are themselves instances. Of metaclasses.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Everything is an object in Python, and they are all either instance of classes
or instances of metaclasses.

Except for type.

type is actually its own metaclass. This is not something you could
reproduce in pure Python, and is done by cheating a little bit at the implementation
level.

Secondly, metaclasses are complicated. You may not want to use them for
very simple class alterations. You can change classes by using two different techniques:

  • monkey patching
  • class decorators

99% of the time you need class alteration, you are better off using these.

But 98% of the time, you don’t need class alteration at all.

Понравилась статья? Поделить с друзьями:
  • Eo3 ошибка стиральной машины hansa
  • Eo3 ошибка стиральной машины candy
  • Eo2 ошибка стиральной машины candy
  • Enumqueryservicesstatus openservice ошибка 1060 указанная служба не установлена
  • Entry start ошибка на лексусе не заводится